From 1ea90410e1df0d80d78a7fb5ddb887916b909f8f Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Tue, 28 Apr 2026 17:15:13 -0700 Subject: [PATCH 1/2] core tests: migrate request permissions tool turns to profiles (#20033) ## Summary This migrates the macOS request-permissions tool tests from legacy `SandboxPolicy` setup to `PermissionProfile` setup. The tests still exercise the same workspace-write baseline and request-permission grants, but the canonical permissions value is now the profile. ## Changes - Replaces the `workspace_write_excluding_tmp()` helper with a `PermissionProfile::workspace_write_with()` helper. - Applies test config through `Permissions::set_permission_profile()`. - Uses `turn_permission_fields()` for `Op::UserTurn` compatibility fields. - Removes the `SandboxPolicy` import from `request_permissions_tool.rs`. ## Verification - `cargo check -p codex-core --tests` --- .../tests/suite/request_permissions_tool.rs | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/codex-rs/core/tests/suite/request_permissions_tool.rs b/codex-rs/core/tests/suite/request_permissions_tool.rs index 8baf14293c..7a24e91bd4 100644 --- a/codex-rs/core/tests/suite/request_permissions_tool.rs +++ b/codex-rs/core/tests/suite/request_permissions_tool.rs @@ -6,11 +6,12 @@ use codex_core::config::Constrained; use codex_features::Feature; use codex_protocol::config_types::ApprovalsReviewer; use codex_protocol::models::FileSystemPermissions; +use codex_protocol::models::PermissionProfile; +use codex_protocol::permissions::NetworkSandboxPolicy; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::Op; use codex_protocol::protocol::ReviewDecision; -use codex_protocol::protocol::SandboxPolicy; use codex_protocol::request_permissions::PermissionGrantScope; use codex_protocol::request_permissions::RequestPermissionProfile; use codex_protocol::request_permissions::RequestPermissionsResponse; @@ -28,6 +29,7 @@ use core_test_support::skip_if_no_network; use core_test_support::skip_if_sandbox; use core_test_support::test_codex::TestCodex; use core_test_support::test_codex::test_codex; +use core_test_support::test_codex::turn_permission_fields; use core_test_support::wait_for_event; use pretty_assertions::assert_eq; use regex_lite::Regex; @@ -70,13 +72,13 @@ fn build_add_file_patch(patch_path: &Path, content: &str) -> String { ) } -fn workspace_write_excluding_tmp() -> SandboxPolicy { - SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - } +fn workspace_write_excluding_tmp() -> PermissionProfile { + PermissionProfile::workspace_write_with( + &[], + NetworkSandboxPolicy::Restricted, + /*exclude_tmpdir_env_var*/ true, + /*exclude_slash_tmp*/ true, + ) } fn requested_directory_write_permissions(path: &Path) -> RequestPermissionProfile { @@ -133,10 +135,12 @@ async fn submit_turn( test: &TestCodex, prompt: &str, approval_policy: AskForApproval, - sandbox_policy: SandboxPolicy, + permission_profile: PermissionProfile, approvals_reviewer: Option, ) -> Result<()> { let session_model = test.session_configured.model.clone(); + let (sandbox_policy, permission_profile) = + turn_permission_fields(permission_profile, test.config.cwd.as_path()); test.codex .submit(Op::UserTurn { environments: None, @@ -149,7 +153,7 @@ async fn submit_turn( approval_policy, approvals_reviewer, sandbox_policy, - permission_profile: None, + permission_profile, model: session_model, effort: None, summary: None, @@ -199,14 +203,15 @@ async fn approved_folder_write_request_permissions_unblocks_later_exec_without_s let server = start_mock_server().await; let approval_policy = AskForApproval::OnRequest; - let sandbox_policy = workspace_write_excluding_tmp(); - let sandbox_policy_for_config = sandbox_policy.clone(); + let permission_profile = workspace_write_excluding_tmp(); + let permission_profile_for_config = permission_profile.clone(); let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); config - .set_legacy_sandbox_policy(sandbox_policy_for_config) - .expect("set sandbox policy"); + .permissions + .set_permission_profile(permission_profile_for_config) + .expect("set permission profile"); config .features .enable(Feature::ExecPermissionApprovals) @@ -258,7 +263,7 @@ async fn approved_folder_write_request_permissions_unblocks_later_exec_without_s &test, "write outside the workspace", approval_policy, - sandbox_policy, + permission_profile, /*approvals_reviewer*/ None, ) .await?; @@ -331,14 +336,15 @@ async fn approved_folder_write_request_permissions_unblocks_later_apply_patch() async fn apply_patch_after_request_permissions(strict_auto_review: bool) -> Result<()> { let server = start_mock_server().await; let approval_policy = AskForApproval::OnRequest; - let sandbox_policy = workspace_write_excluding_tmp(); - let sandbox_policy_for_config = sandbox_policy.clone(); + let permission_profile = workspace_write_excluding_tmp(); + let permission_profile_for_config = permission_profile.clone(); let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); config - .set_legacy_sandbox_policy(sandbox_policy_for_config) - .expect("set sandbox policy"); + .permissions + .set_permission_profile(permission_profile_for_config) + .expect("set permission profile"); config .features .enable(Feature::ExecPermissionApprovals) @@ -415,7 +421,7 @@ async fn apply_patch_after_request_permissions(strict_auto_review: bool) -> Resu &test, "patch outside the workspace", approval_policy, - sandbox_policy, + permission_profile, strict_auto_review.then_some(ApprovalsReviewer::User), ) .await?; From b52283d2ae61219b8843b8760af9bba7f6a85d6c Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Tue, 28 Apr 2026 17:15:24 -0700 Subject: [PATCH 2/2] core tests: migrate zsh-fork permissions to profiles --- codex-rs/core/tests/common/zsh_fork.rs | 28 ++++++++------- codex-rs/core/tests/suite/approvals.rs | 38 +++++++++++++++------ codex-rs/core/tests/suite/skill_approval.rs | 23 +++++++------ 3 files changed, 56 insertions(+), 33 deletions(-) diff --git a/codex-rs/core/tests/common/zsh_fork.rs b/codex-rs/core/tests/common/zsh_fork.rs index 448693e069..e58ebd81dd 100644 --- a/codex-rs/core/tests/common/zsh_fork.rs +++ b/codex-rs/core/tests/common/zsh_fork.rs @@ -5,8 +5,9 @@ use anyhow::Result; use codex_core::config::Config; use codex_core::config::Constrained; use codex_features::Feature; +use codex_protocol::models::PermissionProfile; +use codex_protocol::permissions::NetworkSandboxPolicy; use codex_protocol::protocol::AskForApproval; -use codex_protocol::protocol::SandboxPolicy; use crate::test_codex::TestCodex; use crate::test_codex::test_codex; @@ -22,7 +23,7 @@ impl ZshForkRuntime { &self, config: &mut Config, approval_policy: AskForApproval, - sandbox_policy: SandboxPolicy, + permission_profile: PermissionProfile, ) { config .features @@ -37,18 +38,19 @@ impl ZshForkRuntime { config.permissions.allow_login_shell = false; config.permissions.approval_policy = Constrained::allow_any(approval_policy); config - .set_legacy_sandbox_policy(sandbox_policy) - .expect("set sandbox policy"); + .permissions + .set_permission_profile(permission_profile) + .expect("set permission profile"); } } -pub fn restrictive_workspace_write_policy() -> SandboxPolicy { - SandboxPolicy::WorkspaceWrite { - writable_roots: Vec::new(), - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - } +pub fn restrictive_workspace_write_profile() -> PermissionProfile { + PermissionProfile::workspace_write_with( + &[], + NetworkSandboxPolicy::Restricted, + /*exclude_tmpdir_env_var*/ true, + /*exclude_slash_tmp*/ true, + ) } pub fn zsh_fork_runtime(test_name: &str) -> Result> { @@ -78,7 +80,7 @@ pub async fn build_zsh_fork_test( server: &wiremock::MockServer, runtime: ZshForkRuntime, approval_policy: AskForApproval, - sandbox_policy: SandboxPolicy, + permission_profile: PermissionProfile, pre_build_hook: F, ) -> Result where @@ -87,7 +89,7 @@ where let mut builder = test_codex() .with_pre_build_hook(pre_build_hook) .with_config(move |config| { - runtime.apply_to_config(config, approval_policy, sandbox_policy); + runtime.apply_to_config(config, approval_policy, permission_profile); }); builder.build(server).await } diff --git a/codex-rs/core/tests/suite/approvals.rs b/codex-rs/core/tests/suite/approvals.rs index ef22cfbfc1..79bf0f830f 100644 --- a/codex-rs/core/tests/suite/approvals.rs +++ b/codex-rs/core/tests/suite/approvals.rs @@ -32,10 +32,11 @@ use core_test_support::responses::start_mock_server; use core_test_support::skip_if_no_network; use core_test_support::test_codex::TestCodex; use core_test_support::test_codex::test_codex; +use core_test_support::test_codex::turn_permission_fields; use core_test_support::wait_for_event; use core_test_support::wait_for_event_with_timeout; use core_test_support::zsh_fork::build_zsh_fork_test; -use core_test_support::zsh_fork::restrictive_workspace_write_policy; +use core_test_support::zsh_fork::restrictive_workspace_write_profile; use core_test_support::zsh_fork::zsh_fork_runtime; use pretty_assertions::assert_eq; use regex_lite::Regex; @@ -2318,7 +2319,7 @@ async fn matched_prefix_rule_runs_unsandboxed_under_zsh_fork() -> Result<()> { }; let approval_policy = AskForApproval::Never; - let sandbox_policy = restrictive_workspace_write_policy(); + let permission_profile = restrictive_workspace_write_profile(); let outside_dir = tempfile::tempdir_in(std::env::current_dir()?)?; let outside_path = outside_dir .path() @@ -2332,7 +2333,7 @@ async fn matched_prefix_rule_runs_unsandboxed_under_zsh_fork() -> Result<()> { &server, runtime, approval_policy, - sandbox_policy.clone(), + permission_profile.clone(), move |home| { let _ = fs::remove_file(&outside_path_for_hook); let rules_dir = home.join("rules"); @@ -2367,13 +2368,30 @@ async fn matched_prefix_rule_runs_unsandboxed_under_zsh_fork() -> Result<()> { ) .await; - submit_turn( - &test, - "run allowed touch under zsh fork", - approval_policy, - sandbox_policy, - ) - .await?; + let session_model = test.session_configured.model.clone(); + let (sandbox_policy, permission_profile) = + turn_permission_fields(permission_profile, test.cwd.path()); + test.codex + .submit(Op::UserTurn { + environments: None, + items: vec![UserInput::Text { + text: "run allowed touch under zsh fork".into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + cwd: test.cwd.path().to_path_buf(), + approval_policy, + approvals_reviewer: Some(ApprovalsReviewer::User), + sandbox_policy, + permission_profile, + model: session_model, + effort: None, + summary: None, + service_tier: None, + collaboration_mode: None, + personality: None, + }) + .await?; wait_for_completion_without_approval(&test).await; diff --git a/codex-rs/core/tests/suite/skill_approval.rs b/codex-rs/core/tests/suite/skill_approval.rs index 44c2fb7b68..72244d534e 100644 --- a/codex-rs/core/tests/suite/skill_approval.rs +++ b/codex-rs/core/tests/suite/skill_approval.rs @@ -2,21 +2,22 @@ #![cfg(unix)] use anyhow::Result; +use codex_protocol::models::PermissionProfile; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::ExecApprovalRequestEvent; use codex_protocol::protocol::GranularApprovalConfig; use codex_protocol::protocol::Op; -use codex_protocol::protocol::SandboxPolicy; use codex_protocol::user_input::UserInput; use core_test_support::responses::mount_function_call_agent_response; use core_test_support::responses::start_mock_server; use core_test_support::skip_if_no_network; use core_test_support::test_codex::TestCodex; +use core_test_support::test_codex::turn_permission_fields; use core_test_support::wait_for_event; use core_test_support::wait_for_event_match; use core_test_support::zsh_fork::build_zsh_fork_test; -use core_test_support::zsh_fork::restrictive_workspace_write_policy; +use core_test_support::zsh_fork::restrictive_workspace_write_profile; use core_test_support::zsh_fork::zsh_fork_runtime; use std::fs; use std::path::Path; @@ -40,8 +41,10 @@ async fn submit_turn_with_policies( test: &TestCodex, prompt: &str, approval_policy: AskForApproval, - sandbox_policy: SandboxPolicy, + permission_profile: PermissionProfile, ) -> Result<()> { + let (sandbox_policy, permission_profile) = + turn_permission_fields(permission_profile, test.cwd_path()); test.codex .submit(Op::UserTurn { environments: None, @@ -54,7 +57,7 @@ async fn submit_turn_with_policies( approval_policy, approvals_reviewer: None, sandbox_policy, - permission_profile: None, + permission_profile, model: test.session_configured.model.clone(), effort: None, summary: None, @@ -144,7 +147,7 @@ async fn shell_zsh_fork_skill_scripts_ignore_declared_permissions() -> Result<() request_permissions: true, mcp_elicitations: true, }); - let workspace_write_policy = restrictive_workspace_write_policy(); + let workspace_write_profile = restrictive_workspace_write_profile(); let outside_dir = tempfile::tempdir_in(std::env::current_dir()?)?; let allowed_dir = outside_dir.path().join("allowed-output"); fs::create_dir_all(&allowed_dir)?; @@ -165,7 +168,7 @@ async fn shell_zsh_fork_skill_scripts_ignore_declared_permissions() -> Result<() &server, runtime, approval_policy, - workspace_write_policy.clone(), + workspace_write_profile.clone(), move |home| { let _ = fs::remove_file(&allowed_path_for_hook); write_skill_with_shell_script_contents( @@ -190,7 +193,7 @@ async fn shell_zsh_fork_skill_scripts_ignore_declared_permissions() -> Result<() &test, "use $mbolin-test-skill", approval_policy, - workspace_write_policy, + workspace_write_profile, ) .await?; @@ -235,13 +238,13 @@ async fn shell_zsh_fork_still_enforces_workspace_write_sandbox() -> Result<()> { let server = start_mock_server().await; let tool_call_id = "zsh-fork-workspace-write-deny"; let outside_path = "/tmp/codex-zsh-fork-workspace-write-deny.txt"; - let workspace_write_policy = restrictive_workspace_write_policy(); + let workspace_write_profile = restrictive_workspace_write_profile(); let _ = fs::remove_file(outside_path); let test = build_zsh_fork_test( &server, runtime, AskForApproval::Never, - workspace_write_policy.clone(), + workspace_write_profile.clone(), move |_| { let _ = fs::remove_file(outside_path); }, @@ -258,7 +261,7 @@ async fn shell_zsh_fork_still_enforces_workspace_write_sandbox() -> Result<()> { &test, "write outside workspace with zsh fork", AskForApproval::Never, - workspace_write_policy, + workspace_write_profile, ) .await?;