This commit is contained in:
jimmyfraiture
2025-10-09 13:51:25 +01:00
parent 3aadc93924
commit 29a2305025
4 changed files with 103 additions and 60 deletions

View File

@@ -8,6 +8,9 @@ pub(crate) use runner::ExecutionRequest;
pub(crate) use runner::Executor;
pub(crate) use runner::ExecutorConfig;
pub(crate) use runner::normalize_exec_result;
pub(crate) use sandbox::SandboxLaunch;
pub(crate) use sandbox::SandboxLaunchError;
pub(crate) use sandbox::build_launch_for_sandbox;
pub(crate) use sandbox::request_retry_without_sandbox;
pub(crate) use sandbox::select_sandbox;

View File

@@ -5,15 +5,92 @@ use crate::executor::ExecutionMode;
use crate::executor::ExecutionRequest;
use crate::executor::ExecutorConfig;
use crate::executor::errors::ExecError;
use crate::landlock::create_linux_sandbox_command_args;
use crate::protocol::SandboxPolicy;
use crate::safety::SafetyCheck;
use crate::safety::assess_command_safety;
use crate::safety::assess_patch_safety;
use crate::seatbelt::MACOS_PATH_TO_SEATBELT_EXECUTABLE;
use crate::seatbelt::create_seatbelt_command_args;
use crate::spawn::CODEX_SANDBOX_ENV_VAR;
use crate::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR;
use codex_otel::otel_event_manager::OtelEventManager;
use codex_otel::otel_event_manager::ToolDecisionSource;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::ReviewDecision;
use std::collections::HashMap;
use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;
use thiserror::Error;
#[derive(Debug)]
pub(crate) struct SandboxLaunch {
pub program: String,
pub args: Vec<String>,
pub env: HashMap<String, String>,
}
#[derive(Debug, Error)]
pub(crate) enum SandboxLaunchError {
#[error("missing command line for sandbox launch")]
MissingCommandLine,
#[error("missing codex-linux-sandbox executable path")]
MissingLinuxSandboxExecutable,
}
pub(crate) fn build_launch_for_sandbox(
sandbox: SandboxType,
command: &[String],
sandbox_policy: &SandboxPolicy,
sandbox_policy_cwd: &Path,
codex_linux_sandbox_exe: Option<&PathBuf>,
) -> Result<SandboxLaunch, SandboxLaunchError> {
let mut env = HashMap::new();
if !sandbox_policy.has_full_network_access() {
env.insert(
CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR.to_string(),
"1".to_string(),
);
}
match sandbox {
SandboxType::None => {
let (program, args) = command
.split_first()
.ok_or(SandboxLaunchError::MissingCommandLine)?;
Ok(SandboxLaunch {
program: program.clone(),
args: args.to_vec(),
env,
})
}
SandboxType::MacosSeatbelt => {
env.insert(CODEX_SANDBOX_ENV_VAR.to_string(), "seatbelt".to_string());
let args =
create_seatbelt_command_args(command.to_vec(), sandbox_policy, sandbox_policy_cwd);
Ok(SandboxLaunch {
program: MACOS_PATH_TO_SEATBELT_EXECUTABLE.to_string(),
args,
env,
})
}
SandboxType::LinuxSeccomp => {
let exe =
codex_linux_sandbox_exe.ok_or(SandboxLaunchError::MissingLinuxSandboxExecutable)?;
let args = create_linux_sandbox_command_args(
command.to_vec(),
sandbox_policy,
sandbox_policy_cwd,
);
Ok(SandboxLaunch {
program: exe.to_string_lossy().to_string(),
args,
env,
})
}
}
}
/// Sandbox placement options selected for an execution run, including whether
/// to escalate after failures and whether approvals should persist.

View File

@@ -1,3 +1,4 @@
use crate::executor::SandboxLaunchError;
use thiserror::Error;
#[derive(Debug, Error)]
@@ -24,3 +25,14 @@ impl UnifiedExecError {
Self::CreateSession { pty_error: error }
}
}
impl From<SandboxLaunchError> for UnifiedExecError {
fn from(err: SandboxLaunchError) -> Self {
match err {
SandboxLaunchError::MissingCommandLine => UnifiedExecError::MissingCommandLine,
SandboxLaunchError::MissingLinuxSandboxExecutable => {
UnifiedExecError::MissingLinuxSandboxExecutable
}
}
}
}

View File

@@ -6,8 +6,6 @@ use std::collections::HashMap;
use std::collections::VecDeque;
use std::io::ErrorKind;
use std::io::Read;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::Mutex as StdMutex;
use std::sync::atomic::AtomicBool;
@@ -27,15 +25,11 @@ use crate::exec::SandboxType;
use crate::exec_command::ExecCommandSession;
use crate::executor::ExecutionMode;
use crate::executor::ExecutionRequest;
use crate::executor::SandboxLaunch;
use crate::executor::build_launch_for_sandbox;
use crate::executor::request_retry_without_sandbox;
use crate::executor::select_sandbox;
use crate::landlock::create_linux_sandbox_command_args;
use crate::protocol::ReviewDecision;
use crate::protocol::SandboxPolicy;
use crate::seatbelt::MACOS_PATH_TO_SEATBELT_EXECUTABLE;
use crate::seatbelt::create_seatbelt_command_args;
use crate::spawn::CODEX_SANDBOX_ENV_VAR;
use crate::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR;
use crate::truncate::truncate_middle;
mod errors;
@@ -125,47 +119,6 @@ impl OutputBufferState {
type OutputBuffer = Arc<Mutex<OutputBufferState>>;
type OutputHandles = (OutputBuffer, Arc<Notify>);
fn build_launch_for_sandbox(
sandbox: SandboxType,
command: &[String],
sandbox_policy: &SandboxPolicy,
sandbox_policy_cwd: &Path,
codex_linux_sandbox_exe: Option<&PathBuf>,
) -> Result<(String, Vec<String>, HashMap<String, String>), UnifiedExecError> {
let mut env = HashMap::new();
if !sandbox_policy.has_full_network_access() {
env.insert(
CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR.to_string(),
"1".to_string(),
);
}
match sandbox {
SandboxType::None => {
let (program, args) = command
.split_first()
.ok_or(UnifiedExecError::MissingCommandLine)?;
Ok((program.clone(), args.to_vec(), env))
}
SandboxType::MacosSeatbelt => {
env.insert(CODEX_SANDBOX_ENV_VAR.to_string(), "seatbelt".to_string());
let args =
create_seatbelt_command_args(command.to_vec(), sandbox_policy, sandbox_policy_cwd);
Ok((MACOS_PATH_TO_SEATBELT_EXECUTABLE.to_string(), args, env))
}
SandboxType::LinuxSeccomp => {
let exe =
codex_linux_sandbox_exe.ok_or(UnifiedExecError::MissingLinuxSandboxExecutable)?;
let args = create_linux_sandbox_command_args(
command.to_vec(),
sandbox_policy,
sandbox_policy_cwd,
);
Ok((exe.to_string_lossy().to_string(), args, env))
}
}
}
impl ManagedUnifiedExecSession {
fn new(
session: ExecCommandSession,
@@ -270,7 +223,7 @@ impl UnifiedExecSessionManager {
.record_session_approval(execution_request.approval_command.clone());
}
let (program, args, env) = build_launch_for_sandbox(
let launch = build_launch_for_sandbox(
sandbox_decision.initial_sandbox,
&command,
&context.turn.sandbox_policy,
@@ -278,7 +231,7 @@ impl UnifiedExecSessionManager {
codex_linux_sandbox_exe.as_ref(),
)?;
match create_unified_exec_session(&program, &args, &env).await {
match create_unified_exec_session(&launch).await {
Ok(result) => Ok(result),
Err(err) if sandbox_decision.escalate_on_failure => {
self.retry_without_sandbox(&command, context, err, &otel_event_manager)
@@ -336,14 +289,14 @@ impl UnifiedExecSessionManager {
.notify_background_event(context.sub_id, "retrying command without sandbox")
.await;
let (program, args, env) = build_launch_for_sandbox(
let launch = build_launch_for_sandbox(
SandboxType::None,
command,
&context.turn.sandbox_policy,
&context.turn.cwd,
None,
)?;
create_unified_exec_session(&program, &args, &env).await
create_unified_exec_session(&launch).await
}
ReviewDecision::Denied | ReviewDecision::Abort => Err(UnifiedExecError::UserRejected),
}
@@ -501,9 +454,7 @@ impl UnifiedExecSessionManager {
}
async fn create_unified_exec_session(
program: &str,
args: &[String],
env: &HashMap<String, String>,
launch: &SandboxLaunch,
) -> Result<
(
ExecCommandSession,
@@ -511,7 +462,7 @@ async fn create_unified_exec_session(
),
UnifiedExecError,
> {
if program.is_empty() {
if launch.program.is_empty() {
return Err(UnifiedExecError::MissingCommandLine);
}
@@ -527,11 +478,11 @@ async fn create_unified_exec_session(
.map_err(UnifiedExecError::create_session)?;
// Safe thanks to the check at the top of the function.
let mut command_builder = CommandBuilder::new(program.to_string());
for arg in args {
let mut command_builder = CommandBuilder::new(launch.program.clone());
for arg in &launch.args {
command_builder.arg(arg.clone());
}
for (key, value) in env {
for (key, value) in &launch.env {
command_builder.env(key.clone(), value.clone());
}