#[cfg(target_os = "linux")]use crate::bwrap::WSL1_BWRAP_WARNING;#[cfg(target_os = "linux")]use crate::bwrap::is_wsl1;use crate::landlock::CODEX_LINUX_SANDBOX_ARG0;use crate::landlock::allow_network_for_proxy;use crate::landlock::create_linux_sandbox_command_args_for_permission_profile;use crate::policy_transforms::effective_permission_profile;use crate::policy_transforms::should_require_platform_sandbox;use codex_network_proxy::NetworkProxy;use codex_protocol::config_types::WindowsSandboxLevel;use codex_protocol::models::AdditionalPermissionProfile;use codex_protocol::models::PermissionProfile;use codex_protocol::permissions::FileSystemSandboxPolicy;use codex_protocol::permissions::NetworkSandboxPolicy;use codex_protocol::protocol::SandboxPolicy;use codex_utils_absolute_path::AbsolutePathBuf;use codex_utils_path_uri::PathUri;use std::collections::HashMap;use std::ffi::OsString;use std::io;use std::path::Path;#[derive(Clone, Copy, Debug, PartialEq, Eq)]pub enum SandboxType { None, MacosSeatbelt, LinuxSeccomp, WindowsRestrictedToken,}impl SandboxType { pub fn as_metric_tag(self) -> &'static str { match self { SandboxType::None => "none", SandboxType::MacosSeatbelt => "seatbelt", SandboxType::LinuxSeccomp => "seccomp", SandboxType::WindowsRestrictedToken => "windows_sandbox", } }}#[derive(Clone, Copy, Debug, PartialEq, Eq)]pub enum SandboxablePreference { Auto, Require, Forbid,}pub fn get_platform_sandbox(windows_sandbox_enabled: bool) -> Option<SandboxType> { if cfg!(target_os = "macos") { Some(SandboxType::MacosSeatbelt) } else if cfg!(target_os = "linux") { Some(SandboxType::LinuxSeccomp) } else if cfg!(target_os = "windows") { if windows_sandbox_enabled { Some(SandboxType::WindowsRestrictedToken) } else { None } } else { None }}pub fn with_managed_mitm_ca_readable_root( permission_profile: PermissionProfile, managed_mitm_ca_trust_bundle_path: Option<&AbsolutePathBuf>, sandbox_policy_cwd: &Path,) -> PermissionProfile { let Some(managed_mitm_ca_trust_bundle_path) = managed_mitm_ca_trust_bundle_path else { return permission_profile; }; let (file_system_sandbox_policy, network_sandbox_policy) = permission_profile.to_runtime_permissions(); let file_system_sandbox_policy = file_system_sandbox_policy.with_additional_readable_roots( sandbox_policy_cwd, std::slice::from_ref(managed_mitm_ca_trust_bundle_path), ); PermissionProfile::from_runtime_permissions_with_enforcement( permission_profile.enforcement(), &file_system_sandbox_policy, network_sandbox_policy, )}#[derive(Debug)]pub struct SandboxCommand { pub program: OsString, pub args: Vec<String>, pub cwd: PathUri, pub env: HashMap<String, String>, pub additional_permissions: Option<AdditionalPermissionProfile>,}/// A host-native launch request produced after [`SandboxManager::transform`] validates URI inputs./// Build this only at the execution boundary: in exec-server, or in its logical equivalent within/// app-server. Orchestration and transport code should retain [`PathUri`] values and defer/// conversion to native paths until this request is created.#[derive(Debug)]pub struct SandboxExecRequest { pub command: Vec<String>, pub cwd: AbsolutePathBuf, pub sandbox_policy_cwd: AbsolutePathBuf, pub env: HashMap<String, String>, pub network: Option<NetworkProxy>, pub sandbox: SandboxType, pub windows_sandbox_level: WindowsSandboxLevel, pub windows_sandbox_private_desktop: bool, pub permission_profile: PermissionProfile, pub file_system_sandbox_policy: FileSystemSandboxPolicy, pub network_sandbox_policy: NetworkSandboxPolicy, pub arg0: Option<String>,}/// Bundled arguments for sandbox transformation.////// This keeps call sites self-documenting when several fields are optional.pub struct SandboxTransformRequest<'a> { pub command: SandboxCommand, pub permissions: &'a PermissionProfile, pub sandbox: SandboxType, pub enforce_managed_network: bool, // TODO(viyatb): Evaluate switching this to Option<Arc<NetworkProxy>> // to make shared ownership explicit across runtime/sandbox plumbing. pub network: Option<&'a NetworkProxy>, pub sandbox_policy_cwd: &'a PathUri, pub codex_linux_sandbox_exe: Option<&'a Path>, pub use_legacy_landlock: bool, pub windows_sandbox_level: WindowsSandboxLevel, pub windows_sandbox_private_desktop: bool,}#[derive(Debug)]pub enum SandboxTransformError { InvalidCommandCwd { cwd: PathUri, source: io::Error, }, InvalidSandboxPolicyCwd { cwd: PathUri, source: io::Error, }, MissingLinuxSandboxExecutable, #[cfg(target_os = "linux")] Wsl1UnsupportedForBubblewrap, #[cfg(not(target_os = "macos"))] SeatbeltUnavailable,}impl std::fmt::Display for SandboxTransformError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::InvalidCommandCwd { cwd, source } => { write!( f, "command cwd URI `{cwd}` is not valid on this host: {source}" ) } Self::InvalidSandboxPolicyCwd { cwd, source } => write!( f, "sandbox policy cwd URI `{cwd}` is not valid on this host: {source}" ), Self::MissingLinuxSandboxExecutable => { write!(f, "missing codex-linux-sandbox executable path") } #[cfg(target_os = "linux")] Self::Wsl1UnsupportedForBubblewrap => write!(f, "{WSL1_BWRAP_WARNING}"), #[cfg(not(target_os = "macos"))] Self::SeatbeltUnavailable => write!(f, "seatbelt sandbox is only available on macOS"), } }}impl std::error::Error for SandboxTransformError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::InvalidCommandCwd { source, .. } | Self::InvalidSandboxPolicyCwd { source, .. } => Some(source), Self::MissingLinuxSandboxExecutable => None, #[cfg(target_os = "linux")] Self::Wsl1UnsupportedForBubblewrap => None, #[cfg(not(target_os = "macos"))] Self::SeatbeltUnavailable => None, } }}#[derive(Default)]pub struct SandboxManager;impl SandboxManager { pub fn new() -> Self { Self } pub fn select_initial( &self, file_system_policy: &FileSystemSandboxPolicy, network_policy: NetworkSandboxPolicy, pref: SandboxablePreference, windows_sandbox_level: WindowsSandboxLevel, has_managed_network_requirements: bool, ) -> SandboxType { match pref { SandboxablePreference::Forbid => SandboxType::None, SandboxablePreference::Require => { get_platform_sandbox(windows_sandbox_level != WindowsSandboxLevel::Disabled) .unwrap_or(SandboxType::None) } SandboxablePreference::Auto => { if should_require_platform_sandbox( file_system_policy, network_policy, has_managed_network_requirements, ) { get_platform_sandbox(windows_sandbox_level != WindowsSandboxLevel::Disabled) .unwrap_or(SandboxType::None) } else { SandboxType::None } } } } pub fn transform( &self, request: SandboxTransformRequest<'_>, ) -> Result<SandboxExecRequest, SandboxTransformError> { let SandboxTransformRequest { mut command, permissions, sandbox, enforce_managed_network, network, sandbox_policy_cwd, codex_linux_sandbox_exe, use_legacy_landlock, windows_sandbox_level, windows_sandbox_private_desktop, } = request; let native_command_cwd = command.cwd.to_abs_path().map_err(|source| { SandboxTransformError::InvalidCommandCwd { cwd: command.cwd.clone(), source, } })?; let native_sandbox_policy_cwd = sandbox_policy_cwd.to_abs_path().map_err(|source| { SandboxTransformError::InvalidSandboxPolicyCwd { cwd: sandbox_policy_cwd.clone(), source, } })?; let additional_permissions = command.additional_permissions.take(); let managed_mitm_ca_trust_bundle_path = network.and_then(NetworkProxy::managed_mitm_ca_trust_bundle_path); let effective_permission_profile = effective_permission_profile(permissions, additional_permissions.as_ref()); let effective_permission_profile = with_managed_mitm_ca_readable_root( effective_permission_profile, managed_mitm_ca_trust_bundle_path.as_ref(), native_sandbox_policy_cwd.as_path(), ); let (effective_file_system_policy, effective_network_policy) = effective_permission_profile.to_runtime_permissions(); let mut argv = Vec::with_capacity(1 + command.args.len()); argv.push(command.program); argv.extend(command.args.into_iter().map(OsString::from)); let (argv, arg0_override) = match sandbox { SandboxType::None => (os_argv_to_strings(argv), None), #[cfg(target_os = "macos")] SandboxType::MacosSeatbelt => { use crate::seatbelt::CreateSeatbeltCommandArgsParams; use crate::seatbelt::MACOS_PATH_TO_SEATBELT_EXECUTABLE; use crate::seatbelt::create_seatbelt_command_args; let mut args = create_seatbelt_command_args(CreateSeatbeltCommandArgsParams { command: os_argv_to_strings(argv), file_system_sandbox_policy: &effective_file_system_policy, network_sandbox_policy: effective_network_policy, sandbox_policy_cwd: native_sandbox_policy_cwd.as_path(), enforce_managed_network, network, extra_allow_unix_sockets: &[], }); 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, 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 allow_proxy_network = allow_network_for_proxy(enforce_managed_network); #[cfg(target_os = "linux")] ensure_linux_bubblewrap_is_supported( &effective_file_system_policy, use_legacy_landlock, allow_proxy_network, is_wsl1(), )?; let mut args = create_linux_sandbox_command_args_for_permission_profile( os_argv_to_strings(argv), native_command_cwd.as_path(), &effective_permission_profile, native_sandbox_policy_cwd.as_path(), use_legacy_landlock, allow_proxy_network, ); let mut full_command = Vec::with_capacity(1 + args.len()); full_command.push(os_string_to_command_component(exe.as_os_str().to_owned())); full_command.append(&mut args); (full_command, Some(linux_sandbox_arg0_override(exe))) } #[cfg(target_os = "windows")] SandboxType::WindowsRestrictedToken => (os_argv_to_strings(argv), None), #[cfg(not(target_os = "windows"))] SandboxType::WindowsRestrictedToken => (os_argv_to_strings(argv), None), }; Ok(SandboxExecRequest { command: argv, cwd: native_command_cwd, sandbox_policy_cwd: native_sandbox_policy_cwd, env: command.env, network: network.cloned(), sandbox, windows_sandbox_level, windows_sandbox_private_desktop, permission_profile: effective_permission_profile, file_system_sandbox_policy: effective_file_system_policy, network_sandbox_policy: effective_network_policy, arg0: arg0_override, }) }}pub fn compatibility_sandbox_policy_for_permission_profile( permissions: &PermissionProfile, cwd: &Path,) -> SandboxPolicy { permissions .to_legacy_sandbox_policy(cwd) .unwrap_or_else(|_| { let (file_system_policy, network_policy) = permissions.to_runtime_permissions(); compatibility_workspace_write_policy(file_system_policy, network_policy, cwd) })}fn compatibility_workspace_write_policy( file_system_policy: FileSystemSandboxPolicy, network_policy: NetworkSandboxPolicy, cwd: &Path,) -> SandboxPolicy { let cwd_abs = AbsolutePathBuf::from_absolute_path(cwd).ok(); let writable_roots = file_system_policy .get_writable_roots_with_cwd(cwd) .into_iter() .map(|root| root.root) .filter(|root| cwd_abs.as_ref() != Some(root)) .collect(); let tmpdir_writable = std::env::var_os("TMPDIR") .filter(|tmpdir| !tmpdir.is_empty()) .and_then(|tmpdir| { AbsolutePathBuf::from_absolute_path(std::path::PathBuf::from(tmpdir)).ok() }) .is_some_and(|tmpdir| file_system_policy.can_write_path_with_cwd(tmpdir.as_path(), cwd)); let slash_tmp = Path::new("/tmp"); let slash_tmp_writable = slash_tmp.is_absolute() && slash_tmp.is_dir() && file_system_policy.can_write_path_with_cwd(slash_tmp, cwd); SandboxPolicy::WorkspaceWrite { writable_roots, network_access: network_policy.is_enabled(), exclude_tmpdir_env_var: !tmpdir_writable, exclude_slash_tmp: !slash_tmp_writable, }}#[cfg(target_os = "linux")]fn ensure_linux_bubblewrap_is_supported( file_system_sandbox_policy: &FileSystemSandboxPolicy, use_legacy_landlock: bool, allow_network_for_proxy: bool, is_wsl1: bool,) -> Result<(), SandboxTransformError> { let requires_bubblewrap = allow_network_for_proxy || (!use_legacy_landlock && !file_system_sandbox_policy.has_full_disk_write_access()); if is_wsl1 && requires_bubblewrap { return Err(SandboxTransformError::Wsl1UnsupportedForBubblewrap); } Ok(())}fn os_argv_to_strings(argv: Vec<OsString>) -> Vec<String> { argv.into_iter() .map(os_string_to_command_component) .collect()}fn os_string_to_command_component(value: OsString) -> String { value .into_string() .unwrap_or_else(|value| value.to_string_lossy().into_owned())}fn linux_sandbox_arg0_override(exe: &Path) -> String { if exe.file_name().and_then(|name| name.to_str()) == Some(CODEX_LINUX_SANDBOX_ARG0) { os_string_to_command_component(exe.as_os_str().to_owned()) } else { CODEX_LINUX_SANDBOX_ARG0.to_string() }}#[cfg(test)]#[path = "manager_tests.rs"]mod tests;