diff --git a/codex-rs/core/src/seatbelt_base_policy.sbpl b/codex-rs/core/src/seatbelt_base_policy.sbpl index 824e02803..8ccfa6e82 100644 --- a/codex-rs/core/src/seatbelt_base_policy.sbpl +++ b/codex-rs/core/src/seatbelt_base_policy.sbpl @@ -53,6 +53,7 @@ (sysctl-name "hw.physicalcpu_max") (sysctl-name "hw.tbfrequency_compat") (sysctl-name "hw.vectorunit") + (sysctl-name "kern.argmax") (sysctl-name "kern.hostname") (sysctl-name "kern.maxfilesperproc") (sysctl-name "kern.maxproc") @@ -72,7 +73,8 @@ (sysctl-name-prefix "net.routetable.") ) -; Allow Java to set CPU type grade when required +; Allow Java to read some CPU info. This is misclassified as a "write" because +; userspace passes a memory buffer to the sysctl, but conceptually it is a read. (allow sysctl-write (sysctl-name "kern.grade_cputype")) @@ -86,10 +88,17 @@ (global-name "com.apple.system.opendirectoryd.libinfo") ) -; Added on top of Chrome profile ; Needed for python multiprocessing on MacOS for the SemLock (allow ipc-posix-sem) (allow mach-lookup (global-name "com.apple.PowerManagement.control") ) + +; allow openpty() +(allow pseudo-tty) +(allow file-read* file-write* file-ioctl (literal "/dev/ptmx")) +(allow file-read* file-write* + (require-all + (regex #"^/dev/ttys[0-9]+") + (extension "com.apple.sandbox.pty"))) diff --git a/codex-rs/core/tests/suite/seatbelt.rs b/codex-rs/core/tests/suite/seatbelt.rs index 53175fca1..52150b051 100644 --- a/codex-rs/core/tests/suite/seatbelt.rs +++ b/codex-rs/core/tests/suite/seatbelt.rs @@ -159,23 +159,18 @@ async fn read_only_forbids_all_writes() { .await; } -/// Verify that user lookups via `pwd.getpwuid(os.getuid())` work under the -/// seatbelt sandbox. Prior to allowing the necessary mach‑lookup for -/// OpenDirectory libinfo, this would fail with `KeyError: getpwuid(): uid not found`. #[tokio::test] -async fn python_getpwuid_works_under_seatbelt() { +async fn openpty_works_under_seatbelt() { if std::env::var(CODEX_SANDBOX_ENV_VAR) == Ok("seatbelt".to_string()) { eprintln!("{CODEX_SANDBOX_ENV_VAR} is set to 'seatbelt', skipping test."); return; } - // For local dev. if which::which("python3").is_err() { eprintln!("python3 not found in PATH, skipping test."); return; } - // ReadOnly is sufficient here since we are only exercising user lookup. let policy = SandboxPolicy::ReadOnly; let command_cwd = std::env::current_dir().expect("getcwd"); let sandbox_cwd = command_cwd.clone(); @@ -184,8 +179,12 @@ async fn python_getpwuid_works_under_seatbelt() { vec![ "python3".to_string(), "-c".to_string(), - // Print the passwd struct; success implies lookup worked. - "import pwd, os; print(pwd.getpwuid(os.getuid()))".to_string(), + r#"import os + +master, slave = os.openpty() +os.write(slave, b"ping") +assert os.read(master, 4) == b"ping""# + .to_string(), ], command_cwd, &policy, diff --git a/codex-rs/exec/tests/suite/sandbox.rs b/codex-rs/exec/tests/suite/sandbox.rs index 73a7f0d5b..f0faa8b43 100644 --- a/codex-rs/exec/tests/suite/sandbox.rs +++ b/codex-rs/exec/tests/suite/sandbox.rs @@ -109,6 +109,45 @@ if __name__ == '__main__': assert!(status.success(), "python exited with {status:?}"); } +#[tokio::test] +async fn python_getpwuid_works_under_sandbox() { + core_test_support::skip_if_sandbox!(); + + if std::process::Command::new("python3") + .arg("--version") + .status() + .is_err() + { + eprintln!("python3 not found in PATH, skipping test."); + return; + } + + let policy = SandboxPolicy::ReadOnly; + let command_cwd = std::env::current_dir().expect("should be able to get current dir"); + let sandbox_cwd = command_cwd.clone(); + + let mut child = spawn_command_under_sandbox( + vec![ + "python3".to_string(), + "-c".to_string(), + "import pwd, os; print(pwd.getpwuid(os.getuid()))".to_string(), + ], + command_cwd, + &policy, + sandbox_cwd.as_path(), + StdioPolicy::RedirectForShellTool, + HashMap::new(), + ) + .await + .expect("should be able to spawn python under sandbox"); + + let status = child + .wait() + .await + .expect("should be able to wait for child process"); + assert!(status.success(), "python exited with {status:?}"); +} + #[tokio::test] async fn sandbox_distinguishes_command_and_policy_cwds() { core_test_support::skip_if_sandbox!();