From b097a7a1cb7f4f693867ec430a192bdb49293905 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Thu, 30 Apr 2026 01:42:59 -0700 Subject: [PATCH 1/6] utils: summarize permission profiles directly --- codex-rs/tui/src/status/tests.rs | 6 +- .../sandbox-summary/src/config_summary.rs | 9 +- codex-rs/utils/sandbox-summary/src/lib.rs | 1 - .../sandbox-summary/src/sandbox_summary.rs | 182 +++++++++++------- 4 files changed, 120 insertions(+), 78 deletions(-) diff --git a/codex-rs/tui/src/status/tests.rs b/codex-rs/tui/src/status/tests.rs index bbc1c0ead8..6f6db7b436 100644 --- a/codex-rs/tui/src/status/tests.rs +++ b/codex-rs/tui/src/status/tests.rs @@ -528,6 +528,7 @@ async fn status_snapshot_shows_auto_review_permissions() { async fn status_permissions_full_disk_managed_with_network_is_danger_full_access() { let temp_home = TempDir::new().expect("temp home"); let mut config = test_config(&temp_home).await; + config.approvals_reviewer = ApprovalsReviewer::User; config .permissions .approval_policy @@ -548,9 +549,10 @@ async fn status_permissions_full_disk_managed_with_network_is_danger_full_access } #[tokio::test] -async fn status_permissions_full_disk_managed_without_network_is_external_sandbox() { +async fn status_permissions_full_disk_managed_without_network_is_custom_permissions() { let temp_home = TempDir::new().expect("temp home"); let mut config = test_config(&temp_home).await; + config.approvals_reviewer = ApprovalsReviewer::User; config .permissions .approval_policy @@ -566,7 +568,7 @@ async fn status_permissions_full_disk_managed_without_network_is_external_sandbo assert_eq!( permissions_text_for(&config).as_deref(), - Some("Custom (external-sandbox, on-request)") + Some("Custom (custom permissions, on-request)") ); } diff --git a/codex-rs/utils/sandbox-summary/src/config_summary.rs b/codex-rs/utils/sandbox-summary/src/config_summary.rs index b3de5b6382..f34766f156 100644 --- a/codex-rs/utils/sandbox-summary/src/config_summary.rs +++ b/codex-rs/utils/sandbox-summary/src/config_summary.rs @@ -1,7 +1,7 @@ use codex_core::config::Config; use codex_model_provider_info::WireApi; -use crate::sandbox_summary::summarize_sandbox_policy; +use crate::sandbox_summary::summarize_permission_profile; /// Build a list of key/value pairs summarizing the effective configuration. pub fn create_config_summary_entries(config: &Config, model: &str) -> Vec<(&'static str, String)> { @@ -15,10 +15,9 @@ pub fn create_config_summary_entries(config: &Config, model: &str) -> Vec<(&'sta ), ( "sandbox", - summarize_sandbox_policy( - &config - .permissions - .legacy_sandbox_policy(config.cwd.as_path()), + summarize_permission_profile( + &config.permissions.permission_profile(), + config.cwd.as_path(), ), ), ]; diff --git a/codex-rs/utils/sandbox-summary/src/lib.rs b/codex-rs/utils/sandbox-summary/src/lib.rs index f3da7ab91d..88423e9728 100644 --- a/codex-rs/utils/sandbox-summary/src/lib.rs +++ b/codex-rs/utils/sandbox-summary/src/lib.rs @@ -3,4 +3,3 @@ mod sandbox_summary; pub use config_summary::create_config_summary_entries; pub use sandbox_summary::summarize_permission_profile; -pub use sandbox_summary::summarize_sandbox_policy; diff --git a/codex-rs/utils/sandbox-summary/src/sandbox_summary.rs b/codex-rs/utils/sandbox-summary/src/sandbox_summary.rs index 0719773aad..7396bf2491 100644 --- a/codex-rs/utils/sandbox-summary/src/sandbox_summary.rs +++ b/codex-rs/utils/sandbox-summary/src/sandbox_summary.rs @@ -1,109 +1,151 @@ use codex_protocol::models::PermissionProfile; -use codex_protocol::protocol::NetworkAccess; -use codex_protocol::protocol::SandboxPolicy; +use codex_protocol::permissions::FileSystemSandboxPolicy; +use codex_protocol::permissions::NetworkSandboxPolicy; use std::path::Path; -pub fn summarize_sandbox_policy(sandbox_policy: &SandboxPolicy) -> String { - match sandbox_policy { - SandboxPolicy::DangerFullAccess => "danger-full-access".to_string(), - SandboxPolicy::ReadOnly { network_access, .. } => { - let mut summary = "read-only".to_string(); - if *network_access { - summary.push_str(" (network access enabled)"); - } - summary - } - SandboxPolicy::ExternalSandbox { network_access } => { - let mut summary = "external-sandbox".to_string(); - if matches!(network_access, NetworkAccess::Enabled) { - summary.push_str(" (network access enabled)"); - } - summary - } - SandboxPolicy::WorkspaceWrite { - writable_roots, - network_access, - exclude_tmpdir_env_var, - exclude_slash_tmp, - } => { - let mut summary = "workspace-write".to_string(); - - let mut writable_entries = Vec::::new(); - writable_entries.push("workdir".to_string()); - if !*exclude_slash_tmp { - writable_entries.push("/tmp".to_string()); - } - if !*exclude_tmpdir_env_var { - writable_entries.push("$TMPDIR".to_string()); - } - writable_entries.extend( - writable_roots - .iter() - .map(|p| p.to_string_lossy().to_string()), - ); - - summary.push_str(&format!(" [{}]", writable_entries.join(", "))); - if *network_access { - summary.push_str(" (network access enabled)"); - } - summary +pub fn summarize_permission_profile(permission_profile: &PermissionProfile, cwd: &Path) -> String { + match permission_profile { + PermissionProfile::Disabled => "danger-full-access".to_string(), + PermissionProfile::External { network } => { + summary_with_network("external-sandbox", network.is_enabled()) } + PermissionProfile::Managed { + file_system, + network, + } => summarize_managed_profile(&file_system.to_sandbox_policy(), *network, cwd), } } -pub fn summarize_permission_profile(permission_profile: &PermissionProfile, cwd: &Path) -> String { - match permission_profile.to_legacy_sandbox_policy(cwd) { - Ok(policy) => summarize_sandbox_policy(&policy), - Err(_) => { - if permission_profile.network_sandbox_policy().is_enabled() { - "custom permissions (network access enabled)".to_string() - } else { - "custom permissions".to_string() - } +fn summarize_managed_profile( + file_system: &FileSystemSandboxPolicy, + network: NetworkSandboxPolicy, + cwd: &Path, +) -> String { + let network_enabled = network.is_enabled(); + if file_system.has_full_disk_write_access() { + if network_enabled { + return "danger-full-access".to_string(); } + return custom_summary(network_enabled); } + + let writable_roots = file_system.get_writable_roots_with_cwd(cwd); + if writable_roots.is_empty() { + if file_system.has_full_disk_read_access() { + return summary_with_network("read-only", network_enabled); + } + return custom_summary(network_enabled); + } + + let writable_entries = writable_roots + .iter() + .map(|root| writable_root_display(root.root.as_path(), cwd)) + .collect::>(); + summary_with_network( + &format!("workspace-write [{}]", writable_entries.join(", ")), + network_enabled, + ) +} + +fn writable_root_display(root: &Path, cwd: &Path) -> String { + if root == cwd { + return "workdir".to_string(); + } + if cfg!(unix) && root == Path::new("/tmp") { + return "/tmp".to_string(); + } + if root == std::env::temp_dir() { + return "$TMPDIR".to_string(); + } + root.display().to_string() +} + +fn summary_with_network(base: &str, network_enabled: bool) -> String { + if network_enabled { + format!("{base} (network access enabled)") + } else { + base.to_string() + } +} + +fn custom_summary(network_enabled: bool) -> String { + summary_with_network("custom permissions", network_enabled) } #[cfg(test)] mod tests { use super::*; + use codex_protocol::permissions::FileSystemAccessMode; + use codex_protocol::permissions::FileSystemPath; + use codex_protocol::permissions::FileSystemSandboxEntry; + use codex_protocol::permissions::FileSystemSpecialPath; use codex_utils_absolute_path::AbsolutePathBuf; use pretty_assertions::assert_eq; #[test] fn summarizes_external_sandbox_without_network_access_suffix() { - let summary = summarize_sandbox_policy(&SandboxPolicy::ExternalSandbox { - network_access: NetworkAccess::Restricted, - }); + let summary = summarize_permission_profile( + &PermissionProfile::External { + network: NetworkSandboxPolicy::Restricted, + }, + Path::new("/repo"), + ); assert_eq!(summary, "external-sandbox"); } #[test] fn summarizes_external_sandbox_with_enabled_network() { - let summary = summarize_sandbox_policy(&SandboxPolicy::ExternalSandbox { - network_access: NetworkAccess::Enabled, - }); + let summary = summarize_permission_profile( + &PermissionProfile::External { + network: NetworkSandboxPolicy::Enabled, + }, + Path::new("/repo"), + ); assert_eq!(summary, "external-sandbox (network access enabled)"); } #[test] fn summarizes_read_only_with_enabled_network() { - let summary = summarize_sandbox_policy(&SandboxPolicy::ReadOnly { - network_access: true, - }); + let file_system = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry { + path: FileSystemPath::Special { + value: FileSystemSpecialPath::Root, + }, + access: FileSystemAccessMode::Read, + }]); + let profile = PermissionProfile::from_runtime_permissions( + &file_system, + NetworkSandboxPolicy::Enabled, + ); + let summary = summarize_permission_profile(&profile, Path::new("/repo")); assert_eq!(summary, "read-only (network access enabled)"); } + #[test] + fn unrestricted_filesystem_without_network_is_custom_permissions() { + let profile = PermissionProfile::from_runtime_permissions( + &FileSystemSandboxPolicy::unrestricted(), + NetworkSandboxPolicy::Restricted, + ); + let summary = summarize_permission_profile(&profile, Path::new("/repo")); + assert_eq!(summary, "custom permissions"); + } + #[test] fn workspace_write_summary_still_includes_network_access() { let root = if cfg!(windows) { "C:\\repo" } else { "/repo" }; let writable_root = AbsolutePathBuf::try_from(root).unwrap(); - let summary = summarize_sandbox_policy(&SandboxPolicy::WorkspaceWrite { - writable_roots: vec![writable_root.clone()], - network_access: true, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }); + let cwd = if cfg!(windows) { + "C:\\workdir" + } else { + "/workdir" + }; + let profile = PermissionProfile::workspace_write_with( + std::slice::from_ref(&writable_root), + NetworkSandboxPolicy::Enabled, + /*exclude_tmpdir_env_var*/ true, + /*exclude_slash_tmp*/ true, + ); + let summary = summarize_permission_profile(&profile, Path::new(cwd)); assert_eq!( summary, format!( From 193c91b9bccadad28a7f9315e85d94faf55a8f69 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Thu, 30 Apr 2026 01:46:16 -0700 Subject: [PATCH 2/6] core: build permission instructions from profiles only --- .../src/context/permissions_instructions.rs | 22 ------------------ .../context/permissions_instructions_tests.rs | 23 ------------------- .../core/tests/suite/permissions_messages.rs | 5 ++-- 3 files changed, 2 insertions(+), 48 deletions(-) diff --git a/codex-rs/core/src/context/permissions_instructions.rs b/codex-rs/core/src/context/permissions_instructions.rs index 0ccd6c33a7..d2bf6cf515 100644 --- a/codex-rs/core/src/context/permissions_instructions.rs +++ b/codex-rs/core/src/context/permissions_instructions.rs @@ -8,7 +8,6 @@ use codex_protocol::permissions::NetworkSandboxPolicy; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::GranularApprovalConfig; use codex_protocol::protocol::NetworkAccess; -use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::WritableRoot; use codex_utils_template::Template; use std::path::Path; @@ -85,27 +84,6 @@ impl PermissionsInstructions { ) } - /// Builds permissions instructions from a legacy sandbox policy. - pub fn from_policy( - sandbox_policy: &SandboxPolicy, - approval_policy: AskForApproval, - approvals_reviewer: ApprovalsReviewer, - exec_policy: &Policy, - cwd: &Path, - exec_permission_approvals_enabled: bool, - request_permissions_tool_enabled: bool, - ) -> Self { - Self::from_permission_profile( - &PermissionProfile::from_legacy_sandbox_policy(sandbox_policy), - approval_policy, - approvals_reviewer, - exec_policy, - cwd, - exec_permission_approvals_enabled, - request_permissions_tool_enabled, - ) - } - fn from_permissions_with_network( sandbox_mode: SandboxMode, network_access: NetworkAccess, diff --git a/codex-rs/core/src/context/permissions_instructions_tests.rs b/codex-rs/core/src/context/permissions_instructions_tests.rs index 16d5dc631a..6d1aa5d886 100644 --- a/codex-rs/core/src/context/permissions_instructions_tests.rs +++ b/codex-rs/core/src/context/permissions_instructions_tests.rs @@ -53,29 +53,6 @@ fn builds_permissions_with_network_access_override() { ); } -#[test] -fn builds_permissions_from_policy() { - let policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: true, - exclude_tmpdir_env_var: false, - exclude_slash_tmp: false, - }; - - let instructions = PermissionsInstructions::from_policy( - &policy, - AskForApproval::UnlessTrusted, - ApprovalsReviewer::User, - &Policy::empty(), - &PathBuf::from("/tmp"), - /*exec_permission_approvals_enabled*/ false, - /*request_permissions_tool_enabled*/ false, - ); - let text = instructions.body(); - assert!(text.contains("Network access is enabled.")); - assert!(text.contains("`approval_policy` is `unless-trusted`")); -} - #[test] fn builds_permissions_from_profile() { let cwd = PathBuf::from("/tmp"); diff --git a/codex-rs/core/tests/suite/permissions_messages.rs b/codex-rs/core/tests/suite/permissions_messages.rs index d64349c7f3..8c2bcd27ad 100644 --- a/codex-rs/core/tests/suite/permissions_messages.rs +++ b/codex-rs/core/tests/suite/permissions_messages.rs @@ -575,9 +575,8 @@ async fn permissions_message_includes_writable_roots() -> Result<()> { let permissions = permissions_texts(&req.single_request()); let normalize_line_endings = |s: &str| s.replace("\r\n", "\n"); let exec_policy = load_exec_policy(&test.config.config_layer_stack).await?; - let sandbox_policy = test.config.legacy_sandbox_policy(); - let expected = PermissionsInstructions::from_policy( - &sandbox_policy, + let expected = PermissionsInstructions::from_permission_profile( + &test.config.permissions.permission_profile(), AskForApproval::OnRequest, test.config.approvals_reviewer, &exec_policy, From 918fa6ff7b8eaf22da38da269d238467989d0103 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Thu, 30 Apr 2026 01:49:07 -0700 Subject: [PATCH 3/6] tests: use permission profile fixtures in config checks --- codex-rs/config/src/config_requirements.rs | 71 +++++++------------ .../src/config/network_proxy_spec_tests.rs | 29 ++++---- 2 files changed, 37 insertions(+), 63 deletions(-) diff --git a/codex-rs/config/src/config_requirements.rs b/codex-rs/config/src/config_requirements.rs index 59d1cde9ea..26ff2f1a33 100644 --- a/codex-rs/config/src/config_requirements.rs +++ b/codex-rs/config/src/config_requirements.rs @@ -1198,8 +1198,7 @@ mod tests { use codex_execpolicy::Decision; use codex_execpolicy::Evaluation; use codex_execpolicy::RuleMatch; - use codex_protocol::protocol::NetworkAccess; - use codex_protocol::protocol::SandboxPolicy; + use codex_protocol::permissions::NetworkSandboxPolicy; use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_absolute_path::AbsolutePathBufGuard; use pretty_assertions::assert_eq; @@ -1215,10 +1214,6 @@ mod tests { )?) } - fn profile_from_sandbox_policy(sandbox_policy: &SandboxPolicy) -> PermissionProfile { - PermissionProfile::from_legacy_sandbox_policy(sandbox_policy) - } - fn with_unknown_source(toml: ConfigRequirementsToml) -> ConfigRequirementsWithSources { let ConfigRequirementsToml { allowed_approval_policies, @@ -1767,9 +1762,7 @@ allowed_approvals_reviewers = ["user"] assert_eq!( requirements .permission_profile - .can_set(&profile_from_sandbox_policy( - &SandboxPolicy::DangerFullAccess, - )), + .can_set(&PermissionProfile::Disabled), Err(ConstraintError::InvalidValue { field_name: "sandbox_mode", candidate: "DangerFullAccess".into(), @@ -1914,9 +1907,7 @@ allowed_approvals_reviewers = ["user"] assert!( requirements .permission_profile - .can_set(&profile_from_sandbox_policy( - &SandboxPolicy::new_read_only_policy() - )) + .can_set(&PermissionProfile::read_only()) .is_ok() ); @@ -1999,29 +1990,25 @@ allowed_approvals_reviewers = ["user"] assert!( requirements .permission_profile - .can_set(&profile_from_sandbox_policy( - &SandboxPolicy::new_read_only_policy() - )) + .can_set(&PermissionProfile::read_only()) .is_ok() ); - let workspace_write_policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![AbsolutePathBuf::from_absolute_path(root)?], - network_access: false, - exclude_tmpdir_env_var: false, - exclude_slash_tmp: false, - }; + let workspace_write_profile = PermissionProfile::workspace_write_with( + &[AbsolutePathBuf::from_absolute_path(root)?], + NetworkSandboxPolicy::Restricted, + /*exclude_tmpdir_env_var*/ false, + /*exclude_slash_tmp*/ false, + ); assert!( requirements .permission_profile - .can_set(&profile_from_sandbox_policy(&workspace_write_policy)) + .can_set(&workspace_write_profile) .is_ok() ); assert_eq!( requirements .permission_profile - .can_set(&profile_from_sandbox_policy( - &SandboxPolicy::DangerFullAccess, - )), + .can_set(&PermissionProfile::Disabled), Err(ConstraintError::InvalidValue { field_name: "sandbox_mode", candidate: "DangerFullAccess".into(), @@ -2032,11 +2019,9 @@ allowed_approvals_reviewers = ["user"] assert_eq!( requirements .permission_profile - .can_set(&profile_from_sandbox_policy( - &SandboxPolicy::ExternalSandbox { - network_access: NetworkAccess::Restricted, - } - )), + .can_set(&PermissionProfile::External { + network: NetworkSandboxPolicy::Restricted, + }), Err(ConstraintError::InvalidValue { field_name: "sandbox_mode", candidate: "ExternalSandbox".into(), @@ -2117,24 +2102,22 @@ allowed_approvals_reviewers = ["user"] let requirements = ConfigRequirements::try_from(requirements_with_sources)?; let root = if cfg!(windows) { "C:\\repo" } else { "/repo" }; - let workspace_write_policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![AbsolutePathBuf::from_absolute_path(root)?], - network_access: false, - exclude_tmpdir_env_var: false, - exclude_slash_tmp: false, - }; + let workspace_write_profile = PermissionProfile::workspace_write_with( + &[AbsolutePathBuf::from_absolute_path(root)?], + NetworkSandboxPolicy::Restricted, + /*exclude_tmpdir_env_var*/ false, + /*exclude_slash_tmp*/ false, + ); assert!( requirements .permission_profile - .can_set(&profile_from_sandbox_policy(&workspace_write_policy)) + .can_set(&workspace_write_profile) .is_ok() ); assert_eq!( requirements .permission_profile - .can_set(&profile_from_sandbox_policy( - &SandboxPolicy::DangerFullAccess, - )), + .can_set(&PermissionProfile::Disabled), Err(ConstraintError::InvalidValue { field_name: "sandbox_mode", candidate: "DangerFullAccess".into(), @@ -2165,9 +2148,7 @@ allowed_approvals_reviewers = ["user"] assert_eq!( requirements .permission_profile - .can_set(&profile_from_sandbox_policy( - &SandboxPolicy::DangerFullAccess, - )), + .can_set(&PermissionProfile::Disabled), Err(ConstraintError::InvalidValue { field_name: "sandbox_mode", candidate: "DangerFullAccess".into(), @@ -2206,9 +2187,7 @@ allowed_approvals_reviewers = ["user"] assert_eq!( requirements .permission_profile - .can_set(&profile_from_sandbox_policy( - &SandboxPolicy::new_workspace_write_policy(), - )), + .can_set(&PermissionProfile::workspace_write()), Err(ConstraintError::InvalidValue { field_name: "sandbox_mode", candidate: "WorkspaceWrite".into(), diff --git a/codex-rs/core/src/config/network_proxy_spec_tests.rs b/codex-rs/core/src/config/network_proxy_spec_tests.rs index 14b7c1c330..6dfb1e3a25 100644 --- a/codex-rs/core/src/config/network_proxy_spec_tests.rs +++ b/codex-rs/core/src/config/network_proxy_spec_tests.rs @@ -5,13 +5,8 @@ use codex_network_proxy::NetworkDomainPermission; use codex_protocol::models::ManagedFileSystemPermissions; use codex_protocol::models::PermissionProfile; use codex_protocol::permissions::NetworkSandboxPolicy; -use codex_protocol::protocol::SandboxPolicy; use pretty_assertions::assert_eq; -fn permission_profile_for_sandbox_policy(sandbox_policy: &SandboxPolicy) -> PermissionProfile { - PermissionProfile::from_legacy_sandbox_policy(sandbox_policy) -} - fn domain_permissions( entries: impl IntoIterator, ) -> NetworkDomainPermissionsToml { @@ -62,7 +57,7 @@ fn requirements_allowed_domains_are_a_baseline_for_user_allowlist() { let spec = NetworkProxySpec::from_config_and_constraints( config, Some(requirements), - &permission_profile_for_sandbox_policy(&SandboxPolicy::new_read_only_policy()), + &PermissionProfile::read_only(), ) .expect("config should stay within the managed allowlist"); @@ -97,7 +92,7 @@ fn requirements_allowed_domains_do_not_override_user_denies_for_same_pattern() { let spec = NetworkProxySpec::from_config_and_constraints( config, Some(requirements), - &permission_profile_for_sandbox_policy(&SandboxPolicy::new_workspace_write_policy()), + &PermissionProfile::workspace_write(), ) .expect("managed allowlist should not erase a user deny"); @@ -129,7 +124,7 @@ fn requirements_allowlist_expansion_keeps_user_entries_mutable() { let spec = NetworkProxySpec::from_config_and_constraints( config, Some(requirements), - &permission_profile_for_sandbox_policy(&SandboxPolicy::new_workspace_write_policy()), + &PermissionProfile::workspace_write(), ) .expect("managed baseline should still allow user edits"); @@ -207,7 +202,7 @@ fn danger_full_access_keeps_managed_allowlist_and_denylist_fixed() { let spec = NetworkProxySpec::from_config_and_constraints( config, Some(requirements), - &permission_profile_for_sandbox_policy(&SandboxPolicy::DangerFullAccess), + &PermissionProfile::Disabled, ) .expect("yolo mode should pin the effective policy to the managed baseline"); @@ -241,7 +236,7 @@ fn managed_allowed_domains_only_disables_default_mode_allowlist_expansion() { let spec = NetworkProxySpec::from_config_and_constraints( config, Some(requirements), - &permission_profile_for_sandbox_policy(&SandboxPolicy::new_workspace_write_policy()), + &PermissionProfile::workspace_write(), ) .expect("managed baseline should still load"); @@ -270,7 +265,7 @@ fn managed_allowed_domains_only_ignores_user_allowlist_and_hard_denies_misses() let spec = NetworkProxySpec::from_config_and_constraints( config, Some(requirements), - &permission_profile_for_sandbox_policy(&SandboxPolicy::new_workspace_write_policy()), + &PermissionProfile::workspace_write(), ) .expect("managed-only allowlist should still load"); @@ -300,7 +295,7 @@ fn managed_allowed_domains_only_without_managed_allowlist_blocks_all_user_domain let spec = NetworkProxySpec::from_config_and_constraints( config, Some(requirements), - &permission_profile_for_sandbox_policy(&SandboxPolicy::new_workspace_write_policy()), + &PermissionProfile::workspace_write(), ) .expect("managed-only mode should treat missing managed allowlist as empty"); @@ -324,7 +319,7 @@ fn managed_allowed_domains_only_blocks_all_user_domains_in_full_access_without_m let spec = NetworkProxySpec::from_config_and_constraints( config, Some(requirements), - &permission_profile_for_sandbox_policy(&SandboxPolicy::DangerFullAccess), + &PermissionProfile::Disabled, ) .expect("managed-only mode should treat missing managed allowlist as empty"); @@ -351,7 +346,7 @@ fn deny_only_requirements_do_not_create_allow_constraints_in_full_access() { let spec = NetworkProxySpec::from_config_and_constraints( config, Some(requirements), - &permission_profile_for_sandbox_policy(&SandboxPolicy::DangerFullAccess), + &PermissionProfile::Disabled, ) .expect("deny-only requirements should not constrain the allowlist"); @@ -384,7 +379,7 @@ fn allow_only_requirements_do_not_create_deny_constraints_in_full_access() { let spec = NetworkProxySpec::from_config_and_constraints( config, Some(requirements), - &permission_profile_for_sandbox_policy(&SandboxPolicy::DangerFullAccess), + &PermissionProfile::Disabled, ) .expect("allow-only requirements should not constrain the denylist"); @@ -417,7 +412,7 @@ fn requirements_denied_domains_are_a_baseline_for_default_mode() { let spec = NetworkProxySpec::from_config_and_constraints( config, Some(requirements), - &permission_profile_for_sandbox_policy(&SandboxPolicy::new_workspace_write_policy()), + &PermissionProfile::workspace_write(), ) .expect("default mode should merge managed and user deny entries"); @@ -452,7 +447,7 @@ fn requirements_denylist_expansion_keeps_user_entries_mutable() { let spec = NetworkProxySpec::from_config_and_constraints( config, Some(requirements), - &permission_profile_for_sandbox_policy(&SandboxPolicy::new_workspace_write_policy()), + &PermissionProfile::workspace_write(), ) .expect("managed baseline should still allow user edits"); From 13d7ba47bee83d8fa16304617feeb48b6684a537 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Thu, 30 Apr 2026 01:51:40 -0700 Subject: [PATCH 4/6] tests: use permission profiles in tool sandbox tests --- codex-rs/core/src/sandbox_tags.rs | 14 ----------- codex-rs/core/src/sandbox_tags_tests.rs | 20 +++++++-------- .../src/tools/handlers/apply_patch_tests.rs | 23 ++++++++--------- .../core/src/tools/network_approval_tests.rs | 5 ++-- .../src/tools/runtimes/apply_patch_tests.rs | 5 +--- codex-rs/core/src/tools/sandboxing.rs | 2 -- codex-rs/core/src/tools/sandboxing_tests.rs | 25 +++++++++---------- codex-rs/core/src/turn_metadata_tests.rs | 10 +++++--- 8 files changed, 41 insertions(+), 63 deletions(-) diff --git a/codex-rs/core/src/sandbox_tags.rs b/codex-rs/core/src/sandbox_tags.rs index f6db4da918..2973a5bf94 100644 --- a/codex-rs/core/src/sandbox_tags.rs +++ b/codex-rs/core/src/sandbox_tags.rs @@ -1,24 +1,10 @@ use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::PermissionProfile; -#[cfg(test)] -use codex_protocol::protocol::SandboxPolicy; use codex_sandboxing::SandboxType; use codex_sandboxing::get_platform_sandbox; use codex_sandboxing::policy_transforms::should_require_platform_sandbox; use std::path::Path; -#[cfg(test)] -pub(crate) fn sandbox_tag( - policy: &SandboxPolicy, - windows_sandbox_level: WindowsSandboxLevel, -) -> &'static str { - permission_profile_sandbox_tag( - &PermissionProfile::from_legacy_sandbox_policy(policy), - windows_sandbox_level, - /*enforce_managed_network*/ false, - ) -} - pub(crate) fn permission_profile_sandbox_tag( profile: &PermissionProfile, windows_sandbox_level: WindowsSandboxLevel, diff --git a/codex-rs/core/src/sandbox_tags_tests.rs b/codex-rs/core/src/sandbox_tags_tests.rs index 8b00de9ccd..64dc50574f 100644 --- a/codex-rs/core/src/sandbox_tags_tests.rs +++ b/codex-rs/core/src/sandbox_tags_tests.rs @@ -1,6 +1,5 @@ use super::permission_profile_policy_tag; use super::permission_profile_sandbox_tag; -use super::sandbox_tag; use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::ManagedFileSystemPermissions; use codex_protocol::models::PermissionProfile; @@ -10,8 +9,6 @@ use codex_protocol::permissions::FileSystemSandboxEntry; use codex_protocol::permissions::FileSystemSandboxKind; use codex_protocol::permissions::FileSystemSandboxPolicy; use codex_protocol::permissions::NetworkSandboxPolicy; -use codex_protocol::protocol::NetworkAccess; -use codex_protocol::protocol::SandboxPolicy; use codex_sandboxing::SandboxType; use codex_sandboxing::get_platform_sandbox; use codex_utils_absolute_path::AbsolutePathBuf; @@ -20,29 +17,32 @@ use std::path::Path; #[test] fn danger_full_access_is_untagged_even_when_linux_sandbox_defaults_apply() { - let actual = sandbox_tag( - &SandboxPolicy::DangerFullAccess, + let actual = permission_profile_sandbox_tag( + &PermissionProfile::Disabled, WindowsSandboxLevel::Disabled, + /*enforce_managed_network*/ false, ); assert_eq!(actual, "none"); } #[test] fn external_sandbox_keeps_external_tag_when_linux_sandbox_defaults_apply() { - let actual = sandbox_tag( - &SandboxPolicy::ExternalSandbox { - network_access: NetworkAccess::Enabled, + let actual = permission_profile_sandbox_tag( + &PermissionProfile::External { + network: NetworkSandboxPolicy::Enabled, }, WindowsSandboxLevel::Disabled, + /*enforce_managed_network*/ false, ); assert_eq!(actual, "external"); } #[test] fn default_linux_sandbox_uses_platform_sandbox_tag() { - let actual = sandbox_tag( - &SandboxPolicy::new_read_only_policy(), + let actual = permission_profile_sandbox_tag( + &PermissionProfile::read_only(), WindowsSandboxLevel::Disabled, + /*enforce_managed_network*/ false, ); let expected = get_platform_sandbox(/*windows_sandbox_enabled*/ false) .map(SandboxType::as_metric_tag) diff --git a/codex-rs/core/src/tools/handlers/apply_patch_tests.rs b/codex-rs/core/src/tools/handlers/apply_patch_tests.rs index 230e656225..d8243e2a5c 100644 --- a/codex-rs/core/src/tools/handlers/apply_patch_tests.rs +++ b/codex-rs/core/src/tools/handlers/apply_patch_tests.rs @@ -3,7 +3,6 @@ use codex_apply_patch::MaybeApplyPatchVerified; use codex_exec_server::LOCAL_FS; use codex_protocol::permissions::FileSystemSandboxPolicy; use codex_protocol::protocol::FileChange; -use codex_protocol::protocol::SandboxPolicy; use core_test_support::PathBufExt; use core_test_support::PathExt; use pretty_assertions::assert_eq; @@ -237,12 +236,11 @@ fn write_permissions_for_paths_skip_dirs_already_writable_under_workspace_root() std::fs::create_dir_all(&nested).expect("create nested dir"); let file_path = AbsolutePathBuf::try_from(nested.join("file.txt")) .expect("nested file path should be absolute"); - let sandbox_policy = FileSystemSandboxPolicy::from(&SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: false, - }); + let sandbox_policy = FileSystemSandboxPolicy::workspace_write( + &[], + /*exclude_tmpdir_env_var*/ true, + /*exclude_slash_tmp*/ false, + ); let permissions = write_permissions_for_paths(&[file_path], &sandbox_policy, &cwd); @@ -259,12 +257,11 @@ fn write_permissions_for_paths_keep_dirs_outside_workspace_root() { let file_path = AbsolutePathBuf::try_from(outside.join("file.txt")) .expect("outside file path should be absolute"); let cwd_abs = cwd.abs(); - let sandbox_policy = FileSystemSandboxPolicy::from(&SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }); + let sandbox_policy = FileSystemSandboxPolicy::workspace_write( + &[], + /*exclude_tmpdir_env_var*/ true, + /*exclude_slash_tmp*/ true, + ); let permissions = write_permissions_for_paths(&[file_path], &sandbox_policy, &cwd_abs); let expected_outside = diff --git a/codex-rs/core/src/tools/network_approval_tests.rs b/codex-rs/core/src/tools/network_approval_tests.rs index 37a28b1255..a31d07b2f6 100644 --- a/codex-rs/core/src/tools/network_approval_tests.rs +++ b/codex-rs/core/src/tools/network_approval_tests.rs @@ -4,7 +4,6 @@ use codex_network_proxy::BlockedRequestArgs; use codex_protocol::models::PermissionProfile; use codex_protocol::permissions::NetworkSandboxPolicy; use codex_protocol::protocol::AskForApproval; -use codex_protocol::protocol::SandboxPolicy; use core_test_support::PathBufExt; use core_test_support::test_path_buf; use pretty_assertions::assert_eq; @@ -189,10 +188,10 @@ fn only_never_policy_disables_network_approval_flow() { #[test] fn network_approval_flow_is_limited_to_restricted_sandbox_modes() { assert!(permission_profile_allows_network_approval_flow( - &PermissionProfile::from_legacy_sandbox_policy(&SandboxPolicy::new_read_only_policy()) + &PermissionProfile::read_only() )); assert!(permission_profile_allows_network_approval_flow( - &PermissionProfile::from_legacy_sandbox_policy(&SandboxPolicy::new_workspace_write_policy()) + &PermissionProfile::workspace_write() )); assert!(!permission_profile_allows_network_approval_flow( &PermissionProfile::Disabled diff --git a/codex-rs/core/src/tools/runtimes/apply_patch_tests.rs b/codex-rs/core/src/tools/runtimes/apply_patch_tests.rs index 173fa3e2a0..9437dc9f13 100644 --- a/codex-rs/core/src/tools/runtimes/apply_patch_tests.rs +++ b/codex-rs/core/src/tools/runtimes/apply_patch_tests.rs @@ -4,10 +4,8 @@ use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::AdditionalPermissionProfile; use codex_protocol::models::FileSystemPermissions; use codex_protocol::models::PermissionProfile; -use codex_protocol::permissions::FileSystemSandboxPolicy; use codex_protocol::permissions::NetworkSandboxPolicy; use codex_protocol::protocol::GranularApprovalConfig; -use codex_protocol::protocol::SandboxPolicy; use codex_sandboxing::SandboxManager; use codex_sandboxing::SandboxType; use codex_sandboxing::policy_transforms::effective_file_system_sandbox_policy; @@ -136,8 +134,7 @@ fn file_system_sandbox_context_uses_active_attempt() { additional_permissions: Some(additional_permissions.clone()), permissions_preapproved: false, }; - let sandbox_policy = SandboxPolicy::new_read_only_policy(); - let file_system_policy = FileSystemSandboxPolicy::from(&sandbox_policy); + let file_system_policy = PermissionProfile::read_only().file_system_sandbox_policy(); let permissions = PermissionProfile::from_runtime_permissions( &file_system_policy, NetworkSandboxPolicy::Restricted, diff --git a/codex-rs/core/src/tools/sandboxing.rs b/codex-rs/core/src/tools/sandboxing.rs index 122cd00fad..bcf988cce4 100644 --- a/codex-rs/core/src/tools/sandboxing.rs +++ b/codex-rs/core/src/tools/sandboxing.rs @@ -19,8 +19,6 @@ use codex_protocol::permissions::FileSystemSandboxKind; use codex_protocol::permissions::FileSystemSandboxPolicy; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::ReviewDecision; -#[cfg(test)] -use codex_protocol::protocol::SandboxPolicy; use codex_sandboxing::SandboxCommand; use codex_sandboxing::SandboxManager; use codex_sandboxing::SandboxTransformError; diff --git a/codex-rs/core/src/tools/sandboxing_tests.rs b/codex-rs/core/src/tools/sandboxing_tests.rs index 19eb7a67d9..56cc164657 100644 --- a/codex-rs/core/src/tools/sandboxing_tests.rs +++ b/codex-rs/core/src/tools/sandboxing_tests.rs @@ -1,8 +1,9 @@ use super::*; use crate::sandboxing::SandboxPermissions; use crate::tools::hook_names::HookToolName; +use codex_protocol::models::PermissionProfile; +use codex_protocol::permissions::FileSystemSandboxPolicy; use codex_protocol::protocol::GranularApprovalConfig; -use codex_protocol::protocol::NetworkAccess; use pretty_assertions::assert_eq; use serde_json::json; @@ -36,13 +37,10 @@ fn bash_permission_request_payload_includes_description_when_present() { #[test] fn external_sandbox_skips_exec_approval_on_request() { - let sandbox_policy = SandboxPolicy::ExternalSandbox { - network_access: NetworkAccess::Restricted, - }; assert_eq!( default_exec_approval_requirement( AskForApproval::OnRequest, - &FileSystemSandboxPolicy::from(&sandbox_policy), + &FileSystemSandboxPolicy::external_sandbox(), ), ExecApprovalRequirement::Skip { bypass_sandbox: false, @@ -53,11 +51,10 @@ fn external_sandbox_skips_exec_approval_on_request() { #[test] fn restricted_sandbox_requires_exec_approval_on_request() { - let sandbox_policy = SandboxPolicy::new_read_only_policy(); assert_eq!( default_exec_approval_requirement( AskForApproval::OnRequest, - &FileSystemSandboxPolicy::from(&sandbox_policy) + &PermissionProfile::read_only().file_system_sandbox_policy() ), ExecApprovalRequirement::NeedsApproval { reason: None, @@ -76,9 +73,10 @@ fn default_exec_approval_requirement_rejects_sandbox_prompt_when_granular_disabl mcp_elicitations: true, }); - let sandbox_policy = SandboxPolicy::new_read_only_policy(); - let requirement = - default_exec_approval_requirement(policy, &FileSystemSandboxPolicy::from(&sandbox_policy)); + let requirement = default_exec_approval_requirement( + policy, + &PermissionProfile::read_only().file_system_sandbox_policy(), + ); assert_eq!( requirement, @@ -98,9 +96,10 @@ fn default_exec_approval_requirement_keeps_prompt_when_granular_allows_sandbox_a mcp_elicitations: false, }); - let sandbox_policy = SandboxPolicy::new_read_only_policy(); - let requirement = - default_exec_approval_requirement(policy, &FileSystemSandboxPolicy::from(&sandbox_policy)); + let requirement = default_exec_approval_requirement( + policy, + &PermissionProfile::read_only().file_system_sandbox_policy(), + ); assert_eq!( requirement, diff --git a/codex-rs/core/src/turn_metadata_tests.rs b/codex-rs/core/src/turn_metadata_tests.rs index 6504eadd67..9440b99ac1 100644 --- a/codex-rs/core/src/turn_metadata_tests.rs +++ b/codex-rs/core/src/turn_metadata_tests.rs @@ -1,8 +1,7 @@ use super::*; -use crate::sandbox_tags::sandbox_tag; +use crate::sandbox_tags::permission_profile_sandbox_tag; use codex_protocol::models::PermissionProfile; -use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::SubAgentSource; use core_test_support::PathBufExt; @@ -82,7 +81,6 @@ async fn build_turn_metadata_header_includes_has_changes_for_clean_repo() { fn turn_metadata_state_uses_platform_sandbox_tag() { let temp_dir = TempDir::new().expect("temp dir"); let cwd = temp_dir.path().abs(); - let sandbox_policy = SandboxPolicy::new_read_only_policy(); let permission_profile = PermissionProfile::read_only(); let state = TurnMetadataState::new( @@ -101,7 +99,11 @@ fn turn_metadata_state_uses_platform_sandbox_tag() { let session_id = json.get("session_id").and_then(Value::as_str); let thread_source = json.get("thread_source").and_then(Value::as_str); - let expected_sandbox = sandbox_tag(&sandbox_policy, WindowsSandboxLevel::Disabled); + let expected_sandbox = permission_profile_sandbox_tag( + &permission_profile, + WindowsSandboxLevel::Disabled, + /*enforce_managed_network*/ false, + ); assert_eq!(sandbox_name, Some(expected_sandbox)); assert_eq!(session_id, Some("session-a")); assert_eq!(thread_source, Some("user")); From e93ad189b28919acf609d6a0e5cfb18552118afd Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Thu, 30 Apr 2026 01:59:09 -0700 Subject: [PATCH 5/6] tests: use permission profiles in patch safety checks --- codex-rs/core/src/safety_tests.rs | 117 +++++++++++++----------------- 1 file changed, 52 insertions(+), 65 deletions(-) diff --git a/codex-rs/core/src/safety_tests.rs b/codex-rs/core/src/safety_tests.rs index d699172498..d363a19d5f 100644 --- a/codex-rs/core/src/safety_tests.rs +++ b/codex-rs/core/src/safety_tests.rs @@ -1,18 +1,27 @@ use super::*; use codex_protocol::models::PermissionProfile; -use codex_protocol::protocol::FileSystemAccessMode; -use codex_protocol::protocol::FileSystemPath; -use codex_protocol::protocol::FileSystemSandboxEntry; -use codex_protocol::protocol::FileSystemSpecialPath; +use codex_protocol::permissions::FileSystemAccessMode; +use codex_protocol::permissions::FileSystemPath; +use codex_protocol::permissions::FileSystemSandboxEntry; +use codex_protocol::permissions::FileSystemSpecialPath; +use codex_protocol::permissions::NetworkSandboxPolicy; use codex_protocol::protocol::GranularApprovalConfig; -use codex_protocol::protocol::SandboxPolicy; use codex_utils_absolute_path::AbsolutePathBuf; use core_test_support::PathExt; use pretty_assertions::assert_eq; use tempfile::TempDir; -fn permission_profile_for_policy(sandbox_policy: &SandboxPolicy) -> PermissionProfile { - PermissionProfile::from_legacy_sandbox_policy(sandbox_policy) +fn workspace_write_profile(writable_roots: &[AbsolutePathBuf]) -> PermissionProfile { + PermissionProfile::workspace_write_with( + writable_roots, + NetworkSandboxPolicy::Restricted, + /*exclude_tmpdir_env_var*/ true, + /*exclude_slash_tmp*/ true, + ) +} + +fn file_system_sandbox_policy(profile: &PermissionProfile) -> FileSystemSandboxPolicy { + profile.to_runtime_permissions().0 } #[test] @@ -30,38 +39,30 @@ fn test_writable_roots_constraint() { let add_inside = make_add_change(cwd.join("inner.txt")); let add_outside = make_add_change(parent.join("outside.txt")); - // Policy limited to the workspace only; exclude system temp roots so - // only `cwd` is writable by default. - let policy_workspace_only = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; + // Exclude system temp roots so only the project root is writable by default. + let workspace_only_file_system_policy = + file_system_sandbox_policy(&workspace_write_profile(&[])); assert!(is_write_patch_constrained_to_writable_paths( &add_inside, - &FileSystemSandboxPolicy::from(&policy_workspace_only), + &workspace_only_file_system_policy, &cwd, )); assert!(!is_write_patch_constrained_to_writable_paths( &add_outside, - &FileSystemSandboxPolicy::from(&policy_workspace_only), + &workspace_only_file_system_policy, &cwd, )); // With the parent dir explicitly added as a writable root, the // outside write should be permitted. - let policy_with_parent = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![parent], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; + let parent = AbsolutePathBuf::from_absolute_path(parent).expect("absolute parent"); + let file_system_policy_with_parent = + file_system_sandbox_policy(&workspace_write_profile(&[parent])); assert!(is_write_patch_constrained_to_writable_paths( &add_outside, - &FileSystemSandboxPolicy::from(&policy_with_parent), + &file_system_policy_with_parent, &cwd, )); } @@ -73,16 +74,17 @@ fn external_sandbox_auto_approves_in_on_request() { let add_inside_path = cwd.join("inner.txt"); let add_inside = ApplyPatchAction::new_add_for_test(&add_inside_path, "".to_string()); - let policy = SandboxPolicy::ExternalSandbox { - network_access: codex_protocol::protocol::NetworkAccess::Enabled, + let permission_profile = PermissionProfile::External { + network: NetworkSandboxPolicy::Enabled, }; + let file_system_sandbox_policy = file_system_sandbox_policy(&permission_profile); assert_eq!( assess_patch_safety( &add_inside, AskForApproval::OnRequest, - &permission_profile_for_policy(&policy), - &FileSystemSandboxPolicy::from(&policy), + &permission_profile, + &file_system_sandbox_policy, &cwd, WindowsSandboxLevel::Disabled ), @@ -100,19 +102,15 @@ fn granular_with_all_flags_true_matches_on_request_for_out_of_root_patch() { let parent = cwd.parent().unwrap(); let outside_path = parent.join("outside.txt"); let add_outside = ApplyPatchAction::new_add_for_test(&outside_path, "".to_string()); - let policy_workspace_only = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; + let permission_profile = workspace_write_profile(&[]); + let file_system_sandbox_policy = file_system_sandbox_policy(&permission_profile); assert_eq!( assess_patch_safety( &add_outside, AskForApproval::OnRequest, - &permission_profile_for_policy(&policy_workspace_only), - &FileSystemSandboxPolicy::from(&policy_workspace_only), + &permission_profile, + &file_system_sandbox_policy, &cwd, WindowsSandboxLevel::Disabled, ), @@ -128,8 +126,8 @@ fn granular_with_all_flags_true_matches_on_request_for_out_of_root_patch() { request_permissions: true, mcp_elicitations: true, }), - &permission_profile_for_policy(&policy_workspace_only), - &FileSystemSandboxPolicy::from(&policy_workspace_only), + &permission_profile, + &file_system_sandbox_policy, &cwd, WindowsSandboxLevel::Disabled, ), @@ -144,12 +142,8 @@ fn granular_sandbox_approval_false_rejects_out_of_root_patch() { let parent = cwd.parent().unwrap(); let outside_path = parent.join("outside.txt"); let add_outside = ApplyPatchAction::new_add_for_test(&outside_path, "".to_string()); - let policy_workspace_only = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; + let permission_profile = workspace_write_profile(&[]); + let file_system_sandbox_policy = file_system_sandbox_policy(&permission_profile); assert_eq!( assess_patch_safety( @@ -161,8 +155,8 @@ fn granular_sandbox_approval_false_rejects_out_of_root_patch() { request_permissions: true, mcp_elicitations: true, }), - &permission_profile_for_policy(&policy_workspace_only), - &FileSystemSandboxPolicy::from(&policy_workspace_only), + &permission_profile, + &file_system_sandbox_policy, &cwd, WindowsSandboxLevel::Disabled, ), @@ -178,9 +172,8 @@ fn read_only_policy_rejects_patch_with_read_only_reason() { let cwd = tmp.path().abs(); let inside_path = cwd.join("inside.txt"); let action = ApplyPatchAction::new_add_for_test(&inside_path, "".to_string()); - let sandbox_policy = SandboxPolicy::new_read_only_policy(); - let file_system_sandbox_policy = - FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&sandbox_policy, &cwd); + let permission_profile = PermissionProfile::read_only(); + let file_system_sandbox_policy = file_system_sandbox_policy(&permission_profile); assert!(!is_write_patch_constrained_to_writable_paths( &action, @@ -191,7 +184,7 @@ fn read_only_policy_rejects_patch_with_read_only_reason() { assess_patch_safety( &action, AskForApproval::Never, - &permission_profile_for_policy(&sandbox_policy), + &permission_profile, &file_system_sandbox_policy, &cwd, WindowsSandboxLevel::Disabled, @@ -208,8 +201,8 @@ fn explicit_unreadable_paths_prevent_auto_approval_for_external_sandbox() { let blocked_path = cwd.join("blocked.txt"); let blocked_absolute = blocked_path; let action = ApplyPatchAction::new_add_for_test(&blocked_absolute, "".to_string()); - let sandbox_policy = SandboxPolicy::ExternalSandbox { - network_access: codex_protocol::protocol::NetworkAccess::Restricted, + let permission_profile = PermissionProfile::External { + network: NetworkSandboxPolicy::Restricted, }; let file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(vec![ FileSystemSandboxEntry { @@ -235,7 +228,7 @@ fn explicit_unreadable_paths_prevent_auto_approval_for_external_sandbox() { assess_patch_safety( &action, AskForApproval::OnRequest, - &permission_profile_for_policy(&sandbox_policy), + &permission_profile, &file_system_sandbox_policy, &cwd, WindowsSandboxLevel::Disabled, @@ -252,8 +245,8 @@ fn explicit_read_only_subpaths_prevent_auto_approval_for_external_sandbox() { let blocked_absolute = blocked_path; let docs_absolute = AbsolutePathBuf::resolve_path_against_base("docs", &cwd); let action = ApplyPatchAction::new_add_for_test(&blocked_absolute, "".to_string()); - let sandbox_policy = SandboxPolicy::ExternalSandbox { - network_access: codex_protocol::protocol::NetworkAccess::Restricted, + let permission_profile = PermissionProfile::External { + network: NetworkSandboxPolicy::Restricted, }; let file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(vec![ FileSystemSandboxEntry { @@ -279,7 +272,7 @@ fn explicit_read_only_subpaths_prevent_auto_approval_for_external_sandbox() { assess_patch_safety( &action, AskForApproval::OnRequest, - &permission_profile_for_policy(&sandbox_policy), + &permission_profile, &file_system_sandbox_policy, &cwd, WindowsSandboxLevel::Disabled, @@ -294,14 +287,8 @@ fn missing_project_dot_codex_config_requires_approval() { let cwd = tmp.path().abs(); let config_path = cwd.join(".codex").join("config.toml"); let action = ApplyPatchAction::new_add_for_test(&config_path, "".to_string()); - let sandbox_policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; - let file_system_sandbox_policy = - FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&sandbox_policy, &cwd); + let permission_profile = workspace_write_profile(&[]); + let file_system_sandbox_policy = file_system_sandbox_policy(&permission_profile); assert!(!is_write_patch_constrained_to_writable_paths( &action, @@ -312,7 +299,7 @@ fn missing_project_dot_codex_config_requires_approval() { assess_patch_safety( &action, AskForApproval::OnRequest, - &permission_profile_for_policy(&sandbox_policy), + &permission_profile, &file_system_sandbox_policy, &cwd, WindowsSandboxLevel::Disabled, From 53bc889bad077f506a8fc8714d4d51cebd4b37bc Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Thu, 30 Apr 2026 02:00:50 -0700 Subject: [PATCH 6/6] tests: use permission profiles in unix escalation checks --- .../runtimes/shell/unix_escalation_tests.rs | 31 +++++-------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/codex-rs/core/src/tools/runtimes/shell/unix_escalation_tests.rs b/codex-rs/core/src/tools/runtimes/shell/unix_escalation_tests.rs index 84e469e22d..097bc491ed 100644 --- a/codex-rs/core/src/tools/runtimes/shell/unix_escalation_tests.rs +++ b/codex-rs/core/src/tools/runtimes/shell/unix_escalation_tests.rs @@ -28,7 +28,6 @@ use codex_protocol::permissions::NetworkSandboxPolicy; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::GranularApprovalConfig; use codex_protocol::protocol::GuardianCommandSource; -use codex_protocol::protocol::SandboxPolicy; use codex_sandboxing::SandboxType; use codex_shell_escalation::EscalationExecution; use codex_shell_escalation::EscalationPermissions; @@ -67,10 +66,6 @@ fn read_only_file_system_sandbox_policy() -> FileSystemSandboxPolicy { }]) } -fn permission_profile_from_sandbox_policy(sandbox_policy: &SandboxPolicy) -> PermissionProfile { - PermissionProfile::from_legacy_sandbox_policy(sandbox_policy) -} - fn test_sandbox_cwd() -> AbsolutePathBuf { AbsolutePathBuf::try_from(host_absolute_path(&["workspace"])).unwrap() } @@ -406,9 +401,7 @@ async fn execve_permission_request_hook_short_circuits_prompt() -> anyhow::Resul call_id: "execve-hook-call".to_string(), tool_name: GuardianCommandSource::Shell, approval_policy: AskForApproval::OnRequest, - permission_profile: permission_profile_from_sandbox_policy( - &SandboxPolicy::new_read_only_policy(), - ), + permission_profile: PermissionProfile::read_only(), file_system_sandbox_policy: read_only_file_system_sandbox_policy(), sandbox_policy_cwd: workdir.clone(), sandbox_permissions: SandboxPermissions::RequireEscalated, @@ -475,9 +468,7 @@ fn evaluate_intercepted_exec_policy_uses_wrapper_command_when_shell_wrapper_pars ], InterceptedExecPolicyContext { approval_policy: AskForApproval::OnRequest, - permission_profile: permission_profile_from_sandbox_policy( - &SandboxPolicy::new_read_only_policy(), - ), + permission_profile: PermissionProfile::read_only(), file_system_sandbox_policy: &read_only_file_system_sandbox_policy(), sandbox_cwd: sandbox_cwd.as_path(), sandbox_permissions: SandboxPermissions::UseDefault, @@ -530,9 +521,7 @@ fn evaluate_intercepted_exec_policy_matches_inner_shell_commands_when_enabled() ], InterceptedExecPolicyContext { approval_policy: AskForApproval::OnRequest, - permission_profile: permission_profile_from_sandbox_policy( - &SandboxPolicy::new_read_only_policy(), - ), + permission_profile: PermissionProfile::read_only(), file_system_sandbox_policy: &read_only_file_system_sandbox_policy(), sandbox_cwd: sandbox_cwd.as_path(), sandbox_permissions: SandboxPermissions::UseDefault, @@ -576,9 +565,7 @@ host_executable(name = "git", paths = ["{git_path_literal}"]) &["git".to_string(), "status".to_string()], InterceptedExecPolicyContext { approval_policy: AskForApproval::OnRequest, - permission_profile: permission_profile_from_sandbox_policy( - &SandboxPolicy::new_read_only_policy(), - ), + permission_profile: PermissionProfile::read_only(), file_system_sandbox_policy: &read_only_file_system_sandbox_policy(), sandbox_cwd: sandbox_cwd.as_path(), sandbox_permissions: SandboxPermissions::UseDefault, @@ -610,7 +597,7 @@ fn intercepted_exec_policy_treats_preapproved_additional_permissions_as_default( let program = AbsolutePathBuf::try_from(host_absolute_path(&["usr", "bin", "printf"])).unwrap(); let argv = ["printf".to_string(), "hello".to_string()]; let approval_policy = AskForApproval::OnRequest; - let sandbox_policy = SandboxPolicy::new_workspace_write_policy(); + let permission_profile = PermissionProfile::workspace_write(); let file_system_sandbox_policy = read_only_file_system_sandbox_policy(); let sandbox_cwd = test_sandbox_cwd(); @@ -620,7 +607,7 @@ fn intercepted_exec_policy_treats_preapproved_additional_permissions_as_default( &argv, InterceptedExecPolicyContext { approval_policy, - permission_profile: permission_profile_from_sandbox_policy(&sandbox_policy), + permission_profile: permission_profile.clone(), file_system_sandbox_policy: &file_system_sandbox_policy, sandbox_cwd: sandbox_cwd.as_path(), sandbox_permissions: super::approval_sandbox_permissions( @@ -636,7 +623,7 @@ fn intercepted_exec_policy_treats_preapproved_additional_permissions_as_default( &argv, InterceptedExecPolicyContext { approval_policy, - permission_profile: permission_profile_from_sandbox_policy(&sandbox_policy), + permission_profile, file_system_sandbox_policy: &file_system_sandbox_policy, sandbox_cwd: sandbox_cwd.as_path(), sandbox_permissions: SandboxPermissions::WithAdditionalPermissions, @@ -671,9 +658,7 @@ host_executable(name = "git", paths = ["{allowed_git_literal}"]) &["git".to_string(), "status".to_string()], InterceptedExecPolicyContext { approval_policy: AskForApproval::OnRequest, - permission_profile: permission_profile_from_sandbox_policy( - &SandboxPolicy::new_read_only_policy(), - ), + permission_profile: PermissionProfile::read_only(), file_system_sandbox_policy: &read_only_file_system_sandbox_policy(), sandbox_cwd: sandbox_cwd.as_path(), sandbox_permissions: SandboxPermissions::UseDefault,