sandbox: remove dead seatbelt helper and update tests (#17859)

## Why

`spawn_command_under_seatbelt()` in `codex-rs/core/src/seatbelt.rs` had
fallen out of production use and was only referenced by test-only
wrappers. That left us with sandbox tests that could stay green even if
the actual seatbelt exec path regressed, because production shell
execution now flows through `SandboxManager::transform()` and
`ExecRequest::from_sandbox_exec_request()` instead of that helper.

Removing the dead helper also exposed one downstream `codex-exec`
integration test that still imported it, which broke `just clippy`.

## What Changed

- Removed `codex-rs/core/src/seatbelt.rs` and stopped exporting
`codex_core::seatbelt`.
- Removed the redundant `codex-rs/core/tests/suite/seatbelt.rs` coverage
that only exercised the dead helper.
- Kept the `openpty` regression check, but moved it into
`codex-rs/core/tests/suite/exec.rs` so it now runs through
`process_exec_tool_call()`.
- Fixed the seatbelt denial test in `codex-rs/core/tests/suite/exec.rs`
to use `/usr/bin/touch`, so it actually exercises the sandbox instead of
a nonexistent path.
- Updated `codex-rs/exec/tests/suite/sandbox.rs` on macOS to build the
sandboxed command through `build_exec_request()` and spawn the
transformed command, instead of importing the removed helper.
- Left the lower-level seatbelt policy coverage in
`codex-rs/sandboxing/src/seatbelt_tests.rs`, where the policy generator
is still covered directly.

## Verification

- `cargo test -p codex-core suite::exec::`
- `cargo test -p codex-exec`
- `cargo clippy -p codex-exec --tests -- -D warnings`
This commit is contained in:
Michael Bolin
2026-04-14 20:48:01 -07:00
committed by GitHub
parent e063596c67
commit d34bc66466
6 changed files with 97 additions and 380 deletions

View File

@@ -19,17 +19,67 @@ async fn spawn_command_under_sandbox(
stdio_policy: StdioPolicy,
env: HashMap<String, String>,
) -> std::io::Result<Child> {
use codex_core::seatbelt::spawn_command_under_seatbelt;
spawn_command_under_seatbelt(
command,
command_cwd,
use codex_core::exec::ExecCapturePolicy;
use codex_core::exec::ExecParams;
use codex_core::exec::build_exec_request;
use codex_core::sandboxing::SandboxPermissions;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use std::process::Stdio;
let codex_linux_sandbox_exe = None;
let exec_request = build_exec_request(
ExecParams {
command,
cwd: command_cwd,
expiration: 1000.into(),
capture_policy: ExecCapturePolicy::ShellTool,
env,
network: None,
sandbox_permissions: SandboxPermissions::UseDefault,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false,
justification: None,
arg0: None,
},
sandbox_policy,
&FileSystemSandboxPolicy::from_legacy_sandbox_policy(sandbox_policy, sandbox_cwd),
NetworkSandboxPolicy::from(sandbox_policy),
sandbox_cwd,
stdio_policy,
/*network*/ None,
env,
&codex_linux_sandbox_exe,
/*use_legacy_landlock*/ false,
)
.await
.map_err(|err| io::Error::other(err.to_string()))?;
let (program, args) = exec_request
.command
.split_first()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "command args are empty"))?;
let mut child = tokio::process::Command::new(program);
if let Some(arg0) = exec_request.arg0.as_deref() {
child.arg0(arg0);
}
child.args(args);
child.current_dir(exec_request.cwd);
child.env_clear();
child.envs(exec_request.env);
match stdio_policy {
StdioPolicy::RedirectForShellTool => {
child.stdin(Stdio::null());
child.stdout(Stdio::piped()).stderr(Stdio::piped());
}
StdioPolicy::Inherit => {
child
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit());
}
}
child.kill_on_drop(true).spawn()
}
#[cfg(target_os = "linux")]