Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
6c1f39a
fix(linux-sandbox): preserve detached children
viyatb-oai Mar 17, 2026
e2b7bde
fix(linux-sandbox): scope detached child lifetime
viyatb-oai Mar 17, 2026
38df463
refactor(linux-sandbox): narrow detached child plumbing
viyatb-oai Mar 17, 2026
5ac5801
Merge remote-tracking branch 'origin/main' into codex/viyatb/fix-linu…
viyatb-oai Mar 17, 2026
ebcd105
refactor(sandboxing): own detached child helper args
viyatb-oai Mar 17, 2026
4777867
fix(linux-sandbox): reuse bwrap options in fallback
viyatb-oai Mar 17, 2026
8ad7cb4
refactor(linux-sandbox): address detached child review feedback
viyatb-oai Mar 18, 2026
44b7bcf
refactor(sandboxing): use value helper for detached children
viyatb-oai Mar 18, 2026
31f9a78
refactor(linux-sandbox): simplify detached child builder
viyatb-oai Mar 18, 2026
a6397bb
test(linux-sandbox): annotate landlock bool args
viyatb-oai Mar 18, 2026
72ef1ab
Merge remote-tracking branch 'origin/main' into codex/viyatb/fix-linu…
viyatb-oai Mar 18, 2026
96d855a
fix(cli): update debug sandbox linux args
viyatb-oai Mar 18, 2026
53cc1ef
Merge remote-tracking branch 'origin/main' into codex/viyatb/fix-linu…
viyatb-oai Mar 25, 2026
c14405a
Merge remote-tracking branch 'origin/main' into codex/viyatb/fix-linu…
viyatb-oai Mar 26, 2026
a5f3a2a
Merge branch 'main' into codex/viyatb/fix-linux-sandbox-detached-chil…
viyatb-oai Mar 27, 2026
297b24f
chore: merge origin/main
viyatb-oai Apr 7, 2026
4b36e32
test: fix Linux-only sandbox test initializers
viyatb-oai Apr 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions codex-rs/app-server/src/codex_message_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ use codex_rmcp_client::perform_oauth_login_return_url;
use codex_rollout::state_db::StateDbHandle;
use codex_rollout::state_db::get_state_db;
use codex_rollout::state_db::reconcile_rollout;
use codex_sandboxing::LinuxSandboxDetachedChildren;
use codex_state::StateRuntime;
use codex_state::ThreadMetadata;
use codex_state::ThreadMetadataBuilder;
Expand Down Expand Up @@ -1978,6 +1979,7 @@ impl CodexMessageProcessor {
effective_network_sandbox_policy,
sandbox_cwd.as_path(),
&codex_linux_sandbox_exe,
LinuxSandboxDetachedChildren::Disallow,
use_legacy_landlock,
) {
Ok(exec_request) => {
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/cli/src/debug_sandbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,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::LinuxSandboxDetachedChildren;
use codex_sandboxing::landlock::create_linux_sandbox_command_args_for_policies;
#[cfg(target_os = "macos")]
use codex_sandboxing::seatbelt::create_seatbelt_command_args_for_policies;
Expand Down Expand Up @@ -289,6 +290,7 @@ async fn run_command_under_sandbox(
sandbox_policy_cwd.as_path(),
use_legacy_landlock,
/*allow_network_for_proxy*/ false,
LinuxSandboxDetachedChildren::Disallow,
);
let network_policy = config.permissions.network_sandbox_policy;
spawn_debug_sandbox_child(
Expand Down
5 changes: 5 additions & 0 deletions codex-rs/core/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::ExecCommandOutputDeltaEvent;
use codex_protocol::protocol::ExecOutputStream;
use codex_protocol::protocol::SandboxPolicy;
use codex_sandboxing::LinuxSandboxDetachedChildren;
use codex_sandboxing::SandboxCommand;
use codex_sandboxing::SandboxManager;
use codex_sandboxing::SandboxTransformRequest;
Expand Down Expand Up @@ -220,6 +221,7 @@ pub async fn process_exec_tool_call(
network_sandbox_policy,
sandbox_cwd,
codex_linux_sandbox_exe,
LinuxSandboxDetachedChildren::Allow,
use_legacy_landlock,
)?;

Expand All @@ -229,13 +231,15 @@ pub async fn process_exec_tool_call(

/// Transform a portable exec request into the concrete argv/env that should be
/// spawned under the requested sandbox policy.
#[allow(clippy::too_many_arguments)]
pub fn build_exec_request(
params: ExecParams,
sandbox_policy: &SandboxPolicy,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
sandbox_cwd: &Path,
codex_linux_sandbox_exe: &Option<PathBuf>,
linux_sandbox_detached_children: LinuxSandboxDetachedChildren,
use_legacy_landlock: bool,
) -> Result<ExecRequest> {
let windows_sandbox_level = params.windows_sandbox_level;
Expand Down Expand Up @@ -294,6 +298,7 @@ pub fn build_exec_request(
network: network.as_ref(),
sandbox_policy_cwd: sandbox_cwd,
codex_linux_sandbox_exe: codex_linux_sandbox_exe.as_ref(),
linux_sandbox_detached_children,
use_legacy_landlock,
windows_sandbox_level,
windows_sandbox_private_desktop,
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/core/src/landlock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use codex_network_proxy::NetworkProxy;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::SandboxPolicy;
use codex_sandboxing::LinuxSandboxDetachedChildren;
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_policies;
Expand Down Expand Up @@ -48,6 +49,7 @@ where
sandbox_policy_cwd,
use_legacy_landlock,
allow_network_for_proxy(/*enforce_managed_network*/ false),
LinuxSandboxDetachedChildren::Disallow,
);
let codex_linux_sandbox_exe = codex_linux_sandbox_exe.as_ref();
// Preserve the helper alias when we already have it; otherwise force argv0
Expand Down
1 change: 1 addition & 0 deletions codex-rs/core/src/tools/handlers/multi_agents_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ async fn wait_for_turn_aborted(
EventMsg::TurnAborted(TurnAbortedEvent {
turn_id: Some(ref turn_id),
ref reason,
..
}) if turn_id == expected_turn_id && *reason == expected_reason
) {
break;
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/core/src/tools/js_repl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ use crate::original_image_detail::normalize_output_image_detail;
use crate::sandboxing::ExecOptions;
use crate::tools::ToolRouter;
use crate::tools::context::SharedTurnDiffTracker;
use codex_sandboxing::LinuxSandboxDetachedChildren;
use codex_sandboxing::SandboxCommand;
use codex_sandboxing::SandboxManager;
use codex_sandboxing::SandboxTransformRequest;
Expand Down Expand Up @@ -1069,6 +1070,7 @@ impl JsReplManager {
network: None,
sandbox_policy_cwd: &turn.cwd,
codex_linux_sandbox_exe: turn.codex_linux_sandbox_exe.as_ref(),
linux_sandbox_detached_children: LinuxSandboxDetachedChildren::Disallow,
use_legacy_landlock: turn.features.use_legacy_landlock(),
windows_sandbox_level: turn.windows_sandbox_level,
windows_sandbox_private_desktop: turn
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use codex_protocol::protocol::GuardianCommandSource;
use codex_protocol::protocol::NetworkPolicyRuleAction;
use codex_protocol::protocol::ReviewDecision;
use codex_protocol::protocol::SandboxPolicy;
use codex_sandboxing::LinuxSandboxDetachedChildren;
use codex_sandboxing::SandboxCommand;
use codex_sandboxing::SandboxManager;
use codex_sandboxing::SandboxTransformRequest;
Expand Down Expand Up @@ -845,6 +846,7 @@ impl CoreShellCommandExecutor {
network: self.network.as_ref(),
sandbox_policy_cwd: &self.sandbox_policy_cwd,
codex_linux_sandbox_exe: self.codex_linux_sandbox_exe.as_ref(),
linux_sandbox_detached_children: LinuxSandboxDetachedChildren::Disallow,
use_legacy_landlock: self.use_legacy_landlock,
windows_sandbox_level: self.windows_sandbox_level,
windows_sandbox_private_desktop: false,
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/core/src/tools/sandboxing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::ReviewDecision;
#[cfg(test)]
use codex_protocol::protocol::SandboxPolicy;
use codex_sandboxing::LinuxSandboxDetachedChildren;
use codex_sandboxing::SandboxCommand;
use codex_sandboxing::SandboxManager;
use codex_sandboxing::SandboxTransformError;
Expand Down Expand Up @@ -349,6 +350,7 @@ impl<'a> SandboxAttempt<'a> {
network,
sandbox_policy_cwd: self.sandbox_cwd,
codex_linux_sandbox_exe: self.codex_linux_sandbox_exe,
linux_sandbox_detached_children: LinuxSandboxDetachedChildren::Disallow,
use_legacy_landlock: self.use_legacy_landlock,
windows_sandbox_level: self.windows_sandbox_level,
windows_sandbox_private_desktop: self.windows_sandbox_private_desktop,
Expand Down
41 changes: 38 additions & 3 deletions codex-rs/linux-sandbox/src/bwrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,28 @@ pub(crate) struct BwrapOptions {
pub mount_proc: bool,
/// How networking should be configured inside the bubblewrap sandbox.
pub network_mode: BwrapNetworkMode,
/// Whether the sandbox should terminate with the helper process or allow
/// intentionally detached descendants to outlive it.
pub process_lifetime: BwrapProcessLifetime,
}

impl Default for BwrapOptions {
fn default() -> Self {
Self {
mount_proc: true,
network_mode: BwrapNetworkMode::FullAccess,
process_lifetime: BwrapProcessLifetime::TerminateWithParent,
}
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) enum BwrapProcessLifetime {
#[default]
TerminateWithParent,
AllowDetachedChildren,
}

/// Network policy modes for bubblewrap.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) enum BwrapNetworkMode {
Expand Down Expand Up @@ -121,7 +132,6 @@ pub(crate) fn create_bwrap_command_args(
fn create_bwrap_flags_full_filesystem(command: Vec<String>, options: BwrapOptions) -> BwrapArgs {
let mut args = vec![
"--new-session".to_string(),
"--die-with-parent".to_string(),
"--bind".to_string(),
"/".to_string(),
"/".to_string(),
Expand All @@ -130,6 +140,9 @@ fn create_bwrap_flags_full_filesystem(command: Vec<String>, options: BwrapOption
"--unshare-user".to_string(),
"--unshare-pid".to_string(),
];
if options.process_lifetime == BwrapProcessLifetime::TerminateWithParent {
args.push("--die-with-parent".to_string());
}
if options.network_mode.should_unshare_network() {
args.push("--unshare-net".to_string());
}
Expand Down Expand Up @@ -160,12 +173,14 @@ fn create_bwrap_flags(
let normalized_command_cwd = normalize_command_cwd_for_bwrap(command_cwd);
let mut args = Vec::new();
args.push("--new-session".to_string());
args.push("--die-with-parent".to_string());
args.extend(filesystem_args);
// Request a user namespace explicitly rather than relying on bubblewrap's
// auto-enable behavior, which is skipped when the caller runs as uid 0.
args.push("--unshare-user".to_string());
args.push("--unshare-pid".to_string());
if options.process_lifetime == BwrapProcessLifetime::TerminateWithParent {
args.push("--die-with-parent".to_string());
}
if options.network_mode.should_unshare_network() {
args.push("--unshare-net".to_string());
}
Expand Down Expand Up @@ -620,6 +635,7 @@ mod tests {
BwrapOptions {
mount_proc: true,
network_mode: BwrapNetworkMode::FullAccess,
process_lifetime: BwrapProcessLifetime::TerminateWithParent,
},
)
.expect("create bwrap args");
Expand All @@ -638,6 +654,7 @@ mod tests {
BwrapOptions {
mount_proc: true,
network_mode: BwrapNetworkMode::ProxyOnly,
process_lifetime: BwrapProcessLifetime::TerminateWithParent,
},
)
.expect("create bwrap args");
Expand All @@ -646,12 +663,12 @@ mod tests {
args.args,
vec![
"--new-session".to_string(),
"--die-with-parent".to_string(),
"--bind".to_string(),
"/".to_string(),
"/".to_string(),
"--unshare-user".to_string(),
"--unshare-pid".to_string(),
"--die-with-parent".to_string(),
"--unshare-net".to_string(),
"--proc".to_string(),
"/proc".to_string(),
Expand All @@ -661,6 +678,24 @@ mod tests {
);
}

#[test]
fn allow_detached_children_omits_die_with_parent() {
let args = create_bwrap_command_args(
vec!["/bin/true".to_string()],
&FileSystemSandboxPolicy::from(&SandboxPolicy::DangerFullAccess),
Path::new("/"),
Path::new("/"),
BwrapOptions {
mount_proc: true,
network_mode: BwrapNetworkMode::FullAccess,
process_lifetime: BwrapProcessLifetime::AllowDetachedChildren,
},
)
.expect("create bwrap args");

assert!(!args.args.contains(&"--die-with-parent".to_string()));
}

#[cfg(unix)]
#[test]
fn restricted_policy_chdirs_to_canonical_command_cwd() {
Expand Down
41 changes: 25 additions & 16 deletions codex-rs/linux-sandbox/src/linux_run_main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use std::path::PathBuf;

use crate::bwrap::BwrapNetworkMode;
use crate::bwrap::BwrapOptions;
use crate::bwrap::BwrapProcessLifetime;
use crate::bwrap::create_bwrap_command_args;
use crate::landlock::apply_sandbox_policy_to_current_thread;
use crate::launcher::exec_bwrap;
Expand Down Expand Up @@ -80,6 +81,13 @@ pub struct LandlockCommand {
#[arg(long = "proxy-route-spec", hide = true)]
pub proxy_route_spec: Option<String>,

/// Internal compatibility flag.
///
/// If set, omit bubblewrap's parent-death coupling so intentionally
/// detached descendants can outlive the original helper process.
#[arg(long = "allow-detached-children", hide = true, default_value_t = false)]
pub allow_detached_children: bool,

/// When set, skip mounting a fresh `/proc` even though PID isolation is
/// still enabled. This is primarily intended for restrictive container
/// environments that deny `--proc /proc`.
Expand Down Expand Up @@ -109,6 +117,7 @@ pub fn run_main() -> ! {
apply_seccomp_then_exec,
allow_network_for_proxy,
proxy_route_spec,
allow_detached_children,
no_proc,
command,
} = LandlockCommand::parse();
Expand Down Expand Up @@ -196,14 +205,21 @@ pub fn run_main() -> ! {
proxy_route_spec,
command,
});
let options = BwrapOptions {
mount_proc: !no_proc,
network_mode: bwrap_network_mode(network_sandbox_policy, allow_network_for_proxy),
process_lifetime: if allow_detached_children {
BwrapProcessLifetime::AllowDetachedChildren
} else {
BwrapProcessLifetime::TerminateWithParent
},
};
run_bwrap_with_proc_fallback(
&sandbox_policy_cwd,
command_cwd.as_deref(),
&file_system_sandbox_policy,
network_sandbox_policy,
inner,
!no_proc,
allow_network_for_proxy,
options,
);
}

Expand Down Expand Up @@ -402,32 +418,24 @@ fn run_bwrap_with_proc_fallback(
sandbox_policy_cwd: &Path,
command_cwd: Option<&Path>,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
inner: Vec<String>,
mount_proc: bool,
allow_network_for_proxy: bool,
options: BwrapOptions,
) -> ! {
let network_mode = bwrap_network_mode(network_sandbox_policy, allow_network_for_proxy);
let mut mount_proc = mount_proc;
let mut options = options;
let command_cwd = command_cwd.unwrap_or(sandbox_policy_cwd);

if mount_proc
if options.mount_proc
&& !preflight_proc_mount_support(
sandbox_policy_cwd,
command_cwd,
file_system_sandbox_policy,
network_mode,
options.network_mode,
)
{
// Keep the retry silent so sandbox-internal diagnostics do not leak into the
// child process stderr stream.
mount_proc = false;
options.mount_proc = false;
}

let options = BwrapOptions {
mount_proc,
network_mode,
};
let mut bwrap_args = build_bwrap_argv(
inner,
file_system_sandbox_policy,
Expand Down Expand Up @@ -547,6 +555,7 @@ fn build_preflight_bwrap_argv(
BwrapOptions {
mount_proc: true,
network_mode,
process_lifetime: BwrapProcessLifetime::TerminateWithParent,
},
)
}
Expand Down
Loading
Loading