From dd4bbcf7b61f6f40f4b4b99b21c28d7871d0073a Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Thu, 20 Nov 2025 14:07:49 -0800 Subject: [PATCH] fix(sandbox) allow /bin/ps in seatbelt policy --- codex-rs/core/src/seatbelt_base_policy.sbpl | 4 ++ codex-rs/core/tests/suite/seatbelt.rs | 49 +++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/codex-rs/core/src/seatbelt_base_policy.sbpl b/codex-rs/core/src/seatbelt_base_policy.sbpl index 824e028033..6a0c462034 100644 --- a/codex-rs/core/src/seatbelt_base_policy.sbpl +++ b/codex-rs/core/src/seatbelt_base_policy.sbpl @@ -12,6 +12,10 @@ (allow process-fork) (allow signal (target same-sandbox)) +; Permit /bin/ps to run without sandboxing so Homebrew's `brew shellenv` +; can detect the shell even though /bin/ps is setuid. +(allow process-exec (path "/bin/ps") (with no-sandbox)) + ; Allow cf prefs to work. (allow user-preference-read) diff --git a/codex-rs/core/tests/suite/seatbelt.rs b/codex-rs/core/tests/suite/seatbelt.rs index 53175fca11..b0ac462547 100644 --- a/codex-rs/core/tests/suite/seatbelt.rs +++ b/codex-rs/core/tests/suite/seatbelt.rs @@ -203,6 +203,55 @@ async fn python_getpwuid_works_under_seatbelt() { assert!(status.success(), "python exited with {status:?}"); } +#[tokio::test] +async fn brew_shellenv_runs_under_seatbelt_workspace_write_policy() { + if std::env::var(CODEX_SANDBOX_ENV_VAR) == Ok("seatbelt".to_string()) { + eprintln!("{CODEX_SANDBOX_ENV_VAR} is set to 'seatbelt', skipping test."); + return; + } + + let Ok(brew_path) = which::which("brew") else { + eprintln!("brew not found in PATH, skipping test."); + return; + }; + + let policy = SandboxPolicy::new_workspace_write_policy(); + let command_cwd = std::env::current_dir().expect("getcwd"); + let sandbox_cwd = command_cwd.clone(); + + let mut env: HashMap = std::env::vars().collect(); + env.remove(CODEX_SANDBOX_ENV_VAR); + + let child = spawn_command_under_seatbelt( + vec![ + brew_path.to_string_lossy().to_string(), + "shellenv".to_string(), + ], + command_cwd, + &policy, + sandbox_cwd.as_path(), + StdioPolicy::RedirectForShellTool, + env, + ) + .await + .expect("should be able to spawn brew shellenv under seatbelt"); + + let output = child + .wait_with_output() + .await + .expect("should be able to wait for brew shellenv child"); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "brew shellenv under seatbelt exited with {:?}, stderr: {stderr}", + output.status + ); + assert!( + !stderr.contains("Operation not permitted"), + "brew shellenv under seatbelt should not hit permission errors, stderr: {stderr}" + ); +} + #[tokio::test] async fn java_home_finds_runtime_under_seatbelt() { if std::env::var(CODEX_SANDBOX_ENV_VAR) == Ok("seatbelt".to_string()) {