From 81bc6ca867d37a4da7fab406637256b45f533eb2 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Mon, 1 Jun 2026 09:46:29 -0700 Subject: [PATCH] fix(sandboxing): scope detached children to shell tools Co-authored-by: Codex noreply@openai.com --- .../src/request_processors/command_exec_processor.rs | 1 + codex-rs/cli/src/debug_sandbox.rs | 2 ++ codex-rs/core/src/exec.rs | 4 ++++ codex-rs/core/src/landlock.rs | 2 ++ .../core/src/tools/runtimes/shell/unix_escalation.rs | 2 ++ codex-rs/core/src/tools/sandboxing.rs | 2 ++ codex-rs/exec-server/src/fs_sandbox.rs | 2 ++ codex-rs/exec/tests/suite/sandbox.rs | 2 ++ codex-rs/sandboxing/src/landlock.rs | 6 +++++- codex-rs/sandboxing/src/landlock_tests.rs | 12 ++++++++++++ codex-rs/sandboxing/src/lib.rs | 1 + codex-rs/sandboxing/src/manager.rs | 10 ++++++++++ codex-rs/sandboxing/src/manager_tests.rs | 5 +++++ 13 files changed, 50 insertions(+), 1 deletion(-) diff --git a/codex-rs/app-server/src/request_processors/command_exec_processor.rs b/codex-rs/app-server/src/request_processors/command_exec_processor.rs index 930cc6d18f..7bc6a8e9a4 100644 --- a/codex-rs/app-server/src/request_processors/command_exec_processor.rs +++ b/codex-rs/app-server/src/request_processors/command_exec_processor.rs @@ -320,6 +320,7 @@ impl CommandExecRequestProcessor { &sandbox_cwd, &codex_linux_sandbox_exe, use_legacy_landlock, + codex_core::exec::SandboxProcessLifetime::TerminateWithParent, ) .map_err(|err| internal_error(format!("exec failed: {err}")))?; self.command_exec_manager diff --git a/codex-rs/cli/src/debug_sandbox.rs b/codex-rs/cli/src/debug_sandbox.rs index 36c5e683b2..8ef8088f30 100644 --- a/codex-rs/cli/src/debug_sandbox.rs +++ b/codex-rs/cli/src/debug_sandbox.rs @@ -17,6 +17,7 @@ use codex_core::spawn::CODEX_SANDBOX_ENV_VAR; use codex_core::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR; use codex_protocol::config_types::SandboxMode; use codex_protocol::permissions::NetworkSandboxPolicy; +use codex_sandboxing::SandboxProcessLifetime; use codex_sandboxing::landlock::allow_network_for_proxy; use codex_sandboxing::landlock::create_linux_sandbox_command_args_for_permission_profile; #[cfg(target_os = "macos")] @@ -301,6 +302,7 @@ async fn run_command_under_sandbox( permission_profile_cwd.as_path(), use_legacy_landlock, allow_network_for_proxy(managed_network_requirements_enabled), + SandboxProcessLifetime::TerminateWithParent, ); spawn_debug_sandbox_child( codex_linux_sandbox_exe, diff --git a/codex-rs/core/src/exec.rs b/codex-rs/core/src/exec.rs index a485047a32..bee87516e4 100644 --- a/codex-rs/core/src/exec.rs +++ b/codex-rs/core/src/exec.rs @@ -41,6 +41,7 @@ use codex_protocol::protocol::ExecOutputStream; use codex_protocol::protocol::SandboxPolicy; use codex_sandboxing::SandboxCommand; use codex_sandboxing::SandboxManager; +pub use codex_sandboxing::SandboxProcessLifetime; use codex_sandboxing::SandboxTransformRequest; use codex_sandboxing::SandboxType; use codex_sandboxing::SandboxablePreference; @@ -307,6 +308,7 @@ pub async fn process_exec_tool_call( sandbox_cwd, codex_linux_sandbox_exe, use_legacy_landlock, + SandboxProcessLifetime::AllowDetachedChildren, )?; // Route through the sandboxing module for a single, unified execution path. @@ -321,6 +323,7 @@ pub fn build_exec_request( sandbox_cwd: &AbsolutePathBuf, codex_linux_sandbox_exe: &Option, use_legacy_landlock: bool, + process_lifetime: SandboxProcessLifetime, ) -> Result { let ExecParams { command, @@ -382,6 +385,7 @@ pub fn build_exec_request( sandbox_policy_cwd: sandbox_cwd, codex_linux_sandbox_exe: codex_linux_sandbox_exe.as_deref(), use_legacy_landlock, + process_lifetime, windows_sandbox_level, windows_sandbox_private_desktop, }) diff --git a/codex-rs/core/src/landlock.rs b/codex-rs/core/src/landlock.rs index c117f706e1..e8bc53fe43 100644 --- a/codex-rs/core/src/landlock.rs +++ b/codex-rs/core/src/landlock.rs @@ -3,6 +3,7 @@ use crate::spawn::StdioPolicy; use crate::spawn::spawn_child_async; use codex_network_proxy::NetworkProxy; use codex_protocol::models::PermissionProfile; +use codex_sandboxing::SandboxProcessLifetime; use codex_sandboxing::landlock::CODEX_LINUX_SANDBOX_ARG0; use codex_sandboxing::landlock::allow_network_for_proxy; use codex_sandboxing::landlock::create_linux_sandbox_command_args_for_permission_profile; @@ -41,6 +42,7 @@ where sandbox_policy_cwd, use_legacy_landlock, allow_network_for_proxy(/*enforce_managed_network*/ false), + SandboxProcessLifetime::AllowDetachedChildren, ); let codex_linux_sandbox_exe = codex_linux_sandbox_exe.as_ref(); // Preserve the helper alias when we already have it; otherwise force argv0 diff --git a/codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs b/codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs index fef8db5ca9..a520fdacf4 100644 --- a/codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs +++ b/codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs @@ -43,6 +43,7 @@ use codex_protocol::protocol::NetworkPolicyRuleAction; use codex_protocol::protocol::ReviewDecision; use codex_sandboxing::SandboxCommand; use codex_sandboxing::SandboxManager; +use codex_sandboxing::SandboxProcessLifetime; use codex_sandboxing::SandboxTransformRequest; use codex_sandboxing::SandboxType; use codex_sandboxing::SandboxablePreference; @@ -925,6 +926,7 @@ impl CoreShellCommandExecutor { sandbox_policy_cwd: &self.sandbox_policy_cwd, codex_linux_sandbox_exe: self.codex_linux_sandbox_exe.as_deref(), use_legacy_landlock: self.use_legacy_landlock, + process_lifetime: SandboxProcessLifetime::AllowDetachedChildren, windows_sandbox_level: self.windows_sandbox_level, windows_sandbox_private_desktop: false, })?; diff --git a/codex-rs/core/src/tools/sandboxing.rs b/codex-rs/core/src/tools/sandboxing.rs index 1ca589a5f9..7eda1604a8 100644 --- a/codex-rs/core/src/tools/sandboxing.rs +++ b/codex-rs/core/src/tools/sandboxing.rs @@ -21,6 +21,7 @@ use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::ReviewDecision; use codex_sandboxing::SandboxCommand; use codex_sandboxing::SandboxManager; +use codex_sandboxing::SandboxProcessLifetime; use codex_sandboxing::SandboxTransformError; use codex_sandboxing::SandboxTransformRequest; use codex_sandboxing::SandboxType; @@ -409,6 +410,7 @@ impl<'a> SandboxAttempt<'a> { .codex_linux_sandbox_exe .map(std::path::PathBuf::as_path), use_legacy_landlock: self.use_legacy_landlock, + process_lifetime: SandboxProcessLifetime::AllowDetachedChildren, windows_sandbox_level: self.windows_sandbox_level, windows_sandbox_private_desktop: self.windows_sandbox_private_desktop, }) diff --git a/codex-rs/exec-server/src/fs_sandbox.rs b/codex-rs/exec-server/src/fs_sandbox.rs index 3356791d44..154bc4a750 100644 --- a/codex-rs/exec-server/src/fs_sandbox.rs +++ b/codex-rs/exec-server/src/fs_sandbox.rs @@ -11,6 +11,7 @@ use codex_protocol::permissions::NetworkSandboxPolicy; use codex_sandboxing::SandboxCommand; use codex_sandboxing::SandboxExecRequest; use codex_sandboxing::SandboxManager; +use codex_sandboxing::SandboxProcessLifetime; use codex_sandboxing::SandboxTransformRequest; use codex_sandboxing::SandboxablePreference; use codex_utils_absolute_path::AbsolutePathBuf; @@ -111,6 +112,7 @@ impl FileSystemSandboxRunner { sandbox_policy_cwd: cwd.as_path(), codex_linux_sandbox_exe: self.runtime_paths.codex_linux_sandbox_exe.as_deref(), use_legacy_landlock: sandbox_context.use_legacy_landlock, + process_lifetime: SandboxProcessLifetime::TerminateWithParent, windows_sandbox_level: sandbox_context.windows_sandbox_level, windows_sandbox_private_desktop: sandbox_context.windows_sandbox_private_desktop, }) diff --git a/codex-rs/exec/tests/suite/sandbox.rs b/codex-rs/exec/tests/suite/sandbox.rs index 0a5381b1ef..d73bc7f5f1 100644 --- a/codex-rs/exec/tests/suite/sandbox.rs +++ b/codex-rs/exec/tests/suite/sandbox.rs @@ -22,6 +22,7 @@ async fn spawn_command_under_sandbox( ) -> std::io::Result { use codex_core::exec::ExecCapturePolicy; use codex_core::exec::ExecParams; + use codex_core::exec::SandboxProcessLifetime; use codex_core::exec::build_exec_request; use codex_core::sandboxing::SandboxPermissions; use codex_protocol::config_types::WindowsSandboxLevel; @@ -46,6 +47,7 @@ async fn spawn_command_under_sandbox( sandbox_cwd, &codex_linux_sandbox_exe, /*use_legacy_landlock*/ false, + SandboxProcessLifetime::AllowDetachedChildren, ) .map_err(|err| io::Error::other(err.to_string()))?; diff --git a/codex-rs/sandboxing/src/landlock.rs b/codex-rs/sandboxing/src/landlock.rs index dcf8e4d011..e59c1a85eb 100644 --- a/codex-rs/sandboxing/src/landlock.rs +++ b/codex-rs/sandboxing/src/landlock.rs @@ -1,3 +1,4 @@ +use crate::SandboxProcessLifetime; use codex_protocol::models::PermissionProfile; use std::path::Path; @@ -27,6 +28,7 @@ pub fn create_linux_sandbox_command_args_for_permission_profile( sandbox_policy_cwd: &Path, use_legacy_landlock: bool, allow_network_for_proxy: bool, + process_lifetime: SandboxProcessLifetime, ) -> Vec { let permission_profile_json = serde_json::to_string(permission_profile) .unwrap_or_else(|err| panic!("failed to serialize permission profile: {err}")); @@ -53,7 +55,9 @@ pub fn create_linux_sandbox_command_args_for_permission_profile( if allow_network_for_proxy { linux_cmd.push("--allow-network-for-proxy".to_string()); } - linux_cmd.push("--allow-detached-children".to_string()); + if process_lifetime == SandboxProcessLifetime::AllowDetachedChildren { + linux_cmd.push("--allow-detached-children".to_string()); + } linux_cmd.push("--".to_string()); linux_cmd.extend(command); linux_cmd diff --git a/codex-rs/sandboxing/src/landlock_tests.rs b/codex-rs/sandboxing/src/landlock_tests.rs index 184a04fa0a..fc26475144 100644 --- a/codex-rs/sandboxing/src/landlock_tests.rs +++ b/codex-rs/sandboxing/src/landlock_tests.rs @@ -65,6 +65,7 @@ fn permission_profile_flag_is_included() { cwd, /*use_legacy_landlock*/ true, /*allow_network_for_proxy*/ false, + SandboxProcessLifetime::TerminateWithParent, ); assert_eq!( @@ -77,6 +78,17 @@ fn permission_profile_flag_is_included() { .any(|window| window[0] == "--command-cwd" && window[1] == "/tmp/link"), true ); + assert!(!args.contains(&"--allow-detached-children".to_string())); + + let args = create_linux_sandbox_command_args_for_permission_profile( + vec!["/bin/true".to_string()], + command_cwd, + &permission_profile, + cwd, + /*use_legacy_landlock*/ true, + /*allow_network_for_proxy*/ false, + SandboxProcessLifetime::AllowDetachedChildren, + ); assert!(args.contains(&"--allow-detached-children".to_string())); } diff --git a/codex-rs/sandboxing/src/lib.rs b/codex-rs/sandboxing/src/lib.rs index c70393db8a..04b913b87d 100644 --- a/codex-rs/sandboxing/src/lib.rs +++ b/codex-rs/sandboxing/src/lib.rs @@ -13,6 +13,7 @@ pub use bwrap::system_bwrap_warning; pub use manager::SandboxCommand; pub use manager::SandboxExecRequest; pub use manager::SandboxManager; +pub use manager::SandboxProcessLifetime; pub use manager::SandboxTransformError; pub use manager::SandboxTransformRequest; pub use manager::SandboxType; diff --git a/codex-rs/sandboxing/src/manager.rs b/codex-rs/sandboxing/src/manager.rs index 82c49f7908..0efb8ebdde 100644 --- a/codex-rs/sandboxing/src/manager.rs +++ b/codex-rs/sandboxing/src/manager.rs @@ -85,6 +85,13 @@ pub struct SandboxExecRequest { pub arg0: Option, } +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub enum SandboxProcessLifetime { + #[default] + TerminateWithParent, + AllowDetachedChildren, +} + /// Bundled arguments for sandbox transformation. /// /// This keeps call sites self-documenting when several fields are optional. @@ -99,6 +106,7 @@ pub struct SandboxTransformRequest<'a> { pub sandbox_policy_cwd: &'a Path, pub codex_linux_sandbox_exe: Option<&'a Path>, pub use_legacy_landlock: bool, + pub process_lifetime: SandboxProcessLifetime, pub windows_sandbox_level: WindowsSandboxLevel, pub windows_sandbox_private_desktop: bool, } @@ -178,6 +186,7 @@ impl SandboxManager { sandbox_policy_cwd, codex_linux_sandbox_exe, use_legacy_landlock, + process_lifetime, windows_sandbox_level, windows_sandbox_private_desktop, } = request; @@ -232,6 +241,7 @@ impl SandboxManager { sandbox_policy_cwd, use_legacy_landlock, allow_proxy_network, + process_lifetime, ); let mut full_command = Vec::with_capacity(1 + args.len()); full_command.push(os_string_to_command_component(exe.as_os_str().to_owned())); diff --git a/codex-rs/sandboxing/src/manager_tests.rs b/codex-rs/sandboxing/src/manager_tests.rs index b00e9779be..e01310f79a 100644 --- a/codex-rs/sandboxing/src/manager_tests.rs +++ b/codex-rs/sandboxing/src/manager_tests.rs @@ -1,5 +1,6 @@ use super::SandboxCommand; use super::SandboxManager; +use super::SandboxProcessLifetime; use super::SandboxTransformRequest; use super::SandboxType; use super::SandboxablePreference; @@ -93,6 +94,7 @@ fn transform_preserves_unrestricted_file_system_policy_for_restricted_network() sandbox_policy_cwd: cwd.as_path(), codex_linux_sandbox_exe: None, use_legacy_landlock: false, + process_lifetime: SandboxProcessLifetime::TerminateWithParent, windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_private_desktop: false, }) @@ -144,6 +146,7 @@ fn transform_additional_permissions_enable_network_for_external_sandbox() { sandbox_policy_cwd: cwd.as_path(), codex_linux_sandbox_exe: None, use_legacy_landlock: false, + process_lifetime: SandboxProcessLifetime::TerminateWithParent, windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_private_desktop: false, }) @@ -212,6 +215,7 @@ fn transform_additional_permissions_preserves_denied_entries() { sandbox_policy_cwd: cwd.as_path(), codex_linux_sandbox_exe: None, use_legacy_landlock: false, + process_lifetime: SandboxProcessLifetime::TerminateWithParent, windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_private_desktop: false, }) @@ -265,6 +269,7 @@ fn transform_linux_seccomp_request( sandbox_policy_cwd: cwd.as_path(), codex_linux_sandbox_exe: Some(codex_linux_sandbox_exe), use_legacy_landlock: false, + process_lifetime: SandboxProcessLifetime::TerminateWithParent, windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_private_desktop: false, })