/* Module: sandboxing Build platform wrappers and produce ExecEnv for execution. Owns low‑level sandbox placement and transformation of portable CommandSpec into a ready‑to‑spawn environment. */ pub mod assessment; use crate::exec::ExecToolCallOutput; use crate::exec::SandboxType; use crate::exec::StdoutStream; use crate::exec::execute_exec_env; use crate::landlock::create_linux_sandbox_command_args; use crate::protocol::SandboxPolicy; #[cfg(target_os = "macos")] use crate::seatbelt::MACOS_PATH_TO_SEATBELT_EXECUTABLE; #[cfg(target_os = "macos")] use crate::seatbelt::create_seatbelt_command_args; #[cfg(target_os = "macos")] use crate::spawn::CODEX_SANDBOX_ENV_VAR; use crate::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR; use crate::tools::sandboxing::SandboxablePreference; use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; #[derive(Clone, Debug)] pub struct CommandSpec { pub program: String, pub args: Vec, pub cwd: PathBuf, pub env: HashMap, pub timeout_ms: Option, pub with_escalated_permissions: Option, pub justification: Option, pub max_output_tokens: Option, pub max_output_chars: Option, } #[derive(Clone, Debug)] pub struct ExecEnv { pub command: Vec, pub cwd: PathBuf, pub env: HashMap, pub timeout_ms: Option, pub sandbox: SandboxType, pub with_escalated_permissions: Option, pub justification: Option, pub arg0: Option, pub max_output_tokens: Option, pub max_output_chars: Option, } pub enum SandboxPreference { Auto, Require, Forbid, } #[derive(Debug, thiserror::Error)] pub(crate) enum SandboxTransformError { #[error("missing codex-linux-sandbox executable path")] MissingLinuxSandboxExecutable, #[cfg(not(target_os = "macos"))] #[error("seatbelt sandbox is only available on macOS")] SeatbeltUnavailable, } #[derive(Default)] pub struct SandboxManager; impl SandboxManager { pub fn new() -> Self { Self } pub(crate) fn select_initial( &self, policy: &SandboxPolicy, pref: SandboxablePreference, ) -> SandboxType { match pref { SandboxablePreference::Forbid => SandboxType::None, SandboxablePreference::Require => { // Require a platform sandbox when available; on Windows this // respects the enable_experimental_windows_sandbox feature. crate::safety::get_platform_sandbox().unwrap_or(SandboxType::None) } SandboxablePreference::Auto => match policy { SandboxPolicy::DangerFullAccess => SandboxType::None, _ => crate::safety::get_platform_sandbox().unwrap_or(SandboxType::None), }, } } pub(crate) fn transform( &self, spec: &CommandSpec, policy: &SandboxPolicy, sandbox: SandboxType, sandbox_policy_cwd: &Path, codex_linux_sandbox_exe: Option<&PathBuf>, ) -> Result { let mut env = spec.env.clone(); if !policy.has_full_network_access() { env.insert( CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR.to_string(), "1".to_string(), ); } let mut command = Vec::with_capacity(1 + spec.args.len()); command.push(spec.program.clone()); command.extend(spec.args.iter().cloned()); let (command, sandbox_env, arg0_override) = match sandbox { SandboxType::None => (command, HashMap::new(), None), #[cfg(target_os = "macos")] SandboxType::MacosSeatbelt => { let mut seatbelt_env = HashMap::new(); seatbelt_env.insert(CODEX_SANDBOX_ENV_VAR.to_string(), "seatbelt".to_string()); let mut args = create_seatbelt_command_args(command.clone(), policy, sandbox_policy_cwd); let mut full_command = Vec::with_capacity(1 + args.len()); full_command.push(MACOS_PATH_TO_SEATBELT_EXECUTABLE.to_string()); full_command.append(&mut args); (full_command, seatbelt_env, None) } #[cfg(not(target_os = "macos"))] SandboxType::MacosSeatbelt => return Err(SandboxTransformError::SeatbeltUnavailable), SandboxType::LinuxSeccomp => { let exe = codex_linux_sandbox_exe .ok_or(SandboxTransformError::MissingLinuxSandboxExecutable)?; let mut args = create_linux_sandbox_command_args(command.clone(), policy, sandbox_policy_cwd); let mut full_command = Vec::with_capacity(1 + args.len()); full_command.push(exe.to_string_lossy().to_string()); full_command.append(&mut args); ( full_command, HashMap::new(), Some("codex-linux-sandbox".to_string()), ) } // On Windows, the restricted token sandbox executes in-process via the // codex-windows-sandbox crate. We leave the command unchanged here and // branch during execution based on the sandbox type. #[cfg(target_os = "windows")] SandboxType::WindowsRestrictedToken => (command, HashMap::new(), None), // When building for non-Windows targets, this variant is never constructed. #[cfg(not(target_os = "windows"))] SandboxType::WindowsRestrictedToken => (command, HashMap::new(), None), }; env.extend(sandbox_env); Ok(ExecEnv { command, cwd: spec.cwd.clone(), env, timeout_ms: spec.timeout_ms, sandbox, with_escalated_permissions: spec.with_escalated_permissions, justification: spec.justification.clone(), arg0: arg0_override, max_output_tokens: spec.max_output_tokens, max_output_chars: spec.max_output_chars, }) } pub fn denied(&self, sandbox: SandboxType, out: &ExecToolCallOutput) -> bool { crate::exec::is_likely_sandbox_denied(sandbox, out) } } pub async fn execute_env( env: &ExecEnv, policy: &SandboxPolicy, stdout_stream: Option, ) -> crate::error::Result { execute_exec_env(env.clone(), policy, stdout_stream).await }