From 4d6e2af30fb168c9cbea8f4a3bc4461e51e1bb94 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Thu, 30 Apr 2026 02:26:50 -0700 Subject: [PATCH] exec tests: launch sandbox cases from permission profiles --- codex-rs/exec/tests/suite/sandbox.rs | 83 +++++++++++++++------------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/codex-rs/exec/tests/suite/sandbox.rs b/codex-rs/exec/tests/suite/sandbox.rs index feb1a7b8c8..09e39dc665 100644 --- a/codex-rs/exec/tests/suite/sandbox.rs +++ b/codex-rs/exec/tests/suite/sandbox.rs @@ -1,6 +1,7 @@ #![cfg(unix)] use codex_core::spawn::StdioPolicy; -use codex_protocol::protocol::SandboxPolicy; +use codex_protocol::models::PermissionProfile; +use codex_protocol::permissions::NetworkSandboxPolicy; use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_absolute_path::test_support::PathBufExt; use std::collections::HashMap; @@ -14,7 +15,7 @@ use tokio::process::Child; async fn spawn_command_under_sandbox( command: Vec, command_cwd: AbsolutePathBuf, - sandbox_policy: &SandboxPolicy, + permission_profile: &PermissionProfile, sandbox_cwd: &AbsolutePathBuf, stdio_policy: StdioPolicy, env: HashMap, @@ -24,7 +25,6 @@ async fn spawn_command_under_sandbox( use codex_core::exec::build_exec_request; use codex_core::sandboxing::SandboxPermissions; use codex_protocol::config_types::WindowsSandboxLevel; - use codex_protocol::models::PermissionProfile; use std::process::Stdio; let codex_linux_sandbox_exe = None; @@ -42,7 +42,7 @@ async fn spawn_command_under_sandbox( justification: None, arg0: None, }, - &PermissionProfile::from_legacy_sandbox_policy(sandbox_policy), + permission_profile, sandbox_cwd, &codex_linux_sandbox_exe, /*use_legacy_landlock*/ false, @@ -83,22 +83,20 @@ async fn spawn_command_under_sandbox( async fn spawn_command_under_sandbox( command: Vec, command_cwd: AbsolutePathBuf, - sandbox_policy: &SandboxPolicy, + permission_profile: &PermissionProfile, sandbox_cwd: &AbsolutePathBuf, stdio_policy: StdioPolicy, env: HashMap, ) -> std::io::Result { use codex_core::spawn_command_under_linux_sandbox; - use codex_protocol::models::PermissionProfile; let codex_linux_sandbox_exe = core_test_support::find_codex_linux_sandbox_exe() .map_err(|err| io::Error::new(io::ErrorKind::NotFound, err))?; - let permission_profile = PermissionProfile::from_legacy_sandbox_policy(sandbox_policy); spawn_command_under_linux_sandbox( codex_linux_sandbox_exe, command, command_cwd, - &permission_profile, + permission_profile, sandbox_cwd, /*use_legacy_landlock*/ false, stdio_policy, @@ -118,9 +116,16 @@ async fn spawn_command_under_sandbox( async fn linux_sandbox_test_env() -> Option> { let command_cwd = AbsolutePathBuf::current_dir().ok()?; let sandbox_cwd = command_cwd.clone(); - let policy = SandboxPolicy::new_read_only_policy(); + let permission_profile = PermissionProfile::read_only(); - if can_apply_linux_sandbox_policy(&policy, &command_cwd, &sandbox_cwd, HashMap::new()).await { + if can_apply_linux_sandbox_policy( + &permission_profile, + &command_cwd, + &sandbox_cwd, + HashMap::new(), + ) + .await + { return Some(HashMap::new()); } @@ -135,7 +140,7 @@ async fn linux_sandbox_test_env() -> Option> { /// This is used as a capability probe so sandbox behavior tests only run when /// Landlock enforcement is actually active. async fn can_apply_linux_sandbox_policy( - policy: &SandboxPolicy, + permission_profile: &PermissionProfile, command_cwd: &AbsolutePathBuf, sandbox_cwd: &AbsolutePathBuf, env: HashMap, @@ -143,7 +148,7 @@ async fn can_apply_linux_sandbox_policy( let spawn_result = spawn_command_under_sandbox( vec!["/usr/bin/true".to_string()], command_cwd.clone(), - policy, + permission_profile, sandbox_cwd, StdioPolicy::RedirectForShellTool, env, @@ -180,12 +185,12 @@ async fn python_multiprocessing_lock_works_under_sandbox() { #[cfg(target_os = "linux")] let writable_roots: Vec = vec!["/dev/shm".try_into().unwrap()]; - let policy = SandboxPolicy::WorkspaceWrite { - writable_roots, - network_access: false, - exclude_tmpdir_env_var: false, - exclude_slash_tmp: false, - }; + let permission_profile = PermissionProfile::workspace_write_with( + &writable_roots, + NetworkSandboxPolicy::Restricted, + /*exclude_tmpdir_env_var*/ false, + /*exclude_slash_tmp*/ false, + ); let python_code = r#"import multiprocessing from multiprocessing import Lock, Process @@ -210,7 +215,7 @@ if __name__ == '__main__': python_code.to_string(), ], command_cwd, - &policy, + &permission_profile, &sandbox_cwd, StdioPolicy::Inherit, sandbox_env, @@ -242,7 +247,7 @@ async fn python_getpwuid_works_under_sandbox() { return; } - let policy = SandboxPolicy::new_read_only_policy(); + let permission_profile = PermissionProfile::read_only(); let command_cwd = AbsolutePathBuf::current_dir().expect("should be able to get current dir"); let sandbox_cwd = command_cwd.clone(); @@ -253,7 +258,7 @@ async fn python_getpwuid_works_under_sandbox() { "import pwd, os; print(pwd.getpwuid(os.getuid()))".to_string(), ], command_cwd, - &policy, + &permission_profile, &sandbox_cwd, StdioPolicy::RedirectForShellTool, sandbox_env, @@ -294,12 +299,12 @@ async fn sandbox_distinguishes_command_and_policy_cwds() { // Note writable_roots is empty: verify that `canonical_allowed_path` is // writable only because it is under the sandbox policy cwd, not because it // is under a writable root. - let policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; + let permission_profile = PermissionProfile::workspace_write_with( + &[], + NetworkSandboxPolicy::Restricted, + /*exclude_tmpdir_env_var*/ true, + /*exclude_slash_tmp*/ true, + ); // Attempt to write inside the command cwd, which is outside of the sandbox policy cwd. let mut child = spawn_command_under_sandbox( @@ -309,7 +314,7 @@ async fn sandbox_distinguishes_command_and_policy_cwds() { "echo forbidden > forbidden.txt".to_string(), ], command_root.clone(), - &policy, + &permission_profile, &canonical_sandbox_root, StdioPolicy::Inherit, sandbox_env.clone(), @@ -340,7 +345,7 @@ async fn sandbox_distinguishes_command_and_policy_cwds() { canonical_allowed_path.to_string_lossy().into_owned(), ], command_root, - &policy, + &permission_profile, &canonical_sandbox_root, StdioPolicy::Inherit, sandbox_env, @@ -375,12 +380,12 @@ async fn sandbox_blocks_first_time_dot_codex_creation() { create_dir_all(&repo_root).await.expect("mkdir repo"); let dot_codex = repo_root.join(".codex"); let config_toml = dot_codex.join("config.toml"); - let policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; + let permission_profile = PermissionProfile::workspace_write_with( + &[], + NetworkSandboxPolicy::Restricted, + /*exclude_tmpdir_env_var*/ true, + /*exclude_slash_tmp*/ true, + ); let mut child = spawn_command_under_sandbox( vec![ @@ -390,7 +395,7 @@ async fn sandbox_blocks_first_time_dot_codex_creation() { .to_string(), ], repo_root.clone(), - &policy, + &permission_profile, &repo_root, StdioPolicy::RedirectForShellTool, sandbox_env, @@ -507,7 +512,7 @@ fn unix_sock_body() { async fn allow_unix_socketpair_recvfrom() { run_code_under_sandbox( "allow_unix_socketpair_recvfrom", - &SandboxPolicy::new_read_only_policy(), + &PermissionProfile::read_only(), || async { unix_sock_body() }, ) .await @@ -519,7 +524,7 @@ const IN_SANDBOX_ENV_VAR: &str = "IN_SANDBOX"; #[expect(clippy::expect_used)] pub async fn run_code_under_sandbox( test_selector: &str, - policy: &SandboxPolicy, + permission_profile: &PermissionProfile, child_body: F, ) -> io::Result> where @@ -544,7 +549,7 @@ where let mut child = spawn_command_under_sandbox( cmds, command_cwd, - policy, + permission_profile, &sandbox_cwd, stdio_policy, HashMap::from([("IN_SANDBOX".into(), "1".into())]),