diff --git a/codex-rs/core/src/executor/mod.rs b/codex-rs/core/src/executor/mod.rs index ddfb722b95..aee0e62531 100644 --- a/codex-rs/core/src/executor/mod.rs +++ b/codex-rs/core/src/executor/mod.rs @@ -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; diff --git a/codex-rs/core/src/executor/sandbox.rs b/codex-rs/core/src/executor/sandbox.rs index dd15e62794..a7e26d2c59 100644 --- a/codex-rs/core/src/executor/sandbox.rs +++ b/codex-rs/core/src/executor/sandbox.rs @@ -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, + pub env: HashMap, +} + +#[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 { + 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. diff --git a/codex-rs/core/src/unified_exec/errors.rs b/codex-rs/core/src/unified_exec/errors.rs index a162e0f0e7..d635e5ad85 100644 --- a/codex-rs/core/src/unified_exec/errors.rs +++ b/codex-rs/core/src/unified_exec/errors.rs @@ -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 for UnifiedExecError { + fn from(err: SandboxLaunchError) -> Self { + match err { + SandboxLaunchError::MissingCommandLine => UnifiedExecError::MissingCommandLine, + SandboxLaunchError::MissingLinuxSandboxExecutable => { + UnifiedExecError::MissingLinuxSandboxExecutable + } + } + } +} diff --git a/codex-rs/core/src/unified_exec/mod.rs b/codex-rs/core/src/unified_exec/mod.rs index d2d962060c..4fc85388ad 100644 --- a/codex-rs/core/src/unified_exec/mod.rs +++ b/codex-rs/core/src/unified_exec/mod.rs @@ -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>; type OutputHandles = (OutputBuffer, Arc); -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, HashMap), 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, + 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()); }