From d91bc1561856745b1a32b5516d62b3ba699d7720 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Sat, 16 May 2026 12:12:37 -0700 Subject: [PATCH] test: construct permission profiles directly (#23030) ## Why `SandboxPolicy` is now a legacy compatibility shape, but several tests still built a `SandboxPolicy` only to immediately convert it into `PermissionProfile` for APIs that already accept canonical runtime permissions. Those detours make it harder to audit where legacy sandbox policy is still required, because boundary-only usages are mixed together with ordinary test setup. ## What Changed - Updated tests in `codex-core`, `codex-exec`, `codex-analytics`, and `codex-config` to construct `PermissionProfile` values directly when the code under test takes a permission profile. - Changed exec-policy, request-permissions, session, and sandbox test helpers to pass `PermissionProfile` through instead of converting from `SandboxPolicy` internally. - Left `SandboxPolicy` in place where tests are explicitly exercising legacy compatibility or request/response boundaries. ## Test Plan - `cargo test -p codex-analytics -p codex-config` - `cargo test -p codex-core --lib safety::tests` - `cargo test -p codex-core --lib exec_policy::tests::` - `cargo test -p codex-core --lib exec::tests` - `cargo test -p codex-core --lib guardian_review_session_config` - `cargo test -p codex-core --lib tools::network_approval::tests` - `cargo test -p codex-core --lib tools::runtimes::shell::unix_escalation::tests` - `cargo test -p codex-core --lib managed_network` - `cargo test -p codex-core --test all request_permissions::` - `cargo test -p codex-exec sandbox` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23030). * #23036 * __->__ #23030 --- .../analytics/src/analytics_client_tests.rs | 5 +- codex-rs/config/src/config_requirements.rs | 71 ++++------ .../core/src/config/config_loader_tests.rs | 23 +--- codex-rs/core/src/config/config_tests.rs | 46 +++---- codex-rs/core/src/exec_policy_tests.rs | 116 ++++++---------- .../core/src/exec_policy_windows_tests.rs | 10 +- codex-rs/core/src/exec_tests.rs | 3 +- codex-rs/core/src/guardian/tests.rs | 7 +- codex-rs/core/src/safety_tests.rs | 125 +++++++++--------- codex-rs/core/src/session/tests.rs | 28 ++-- .../core/src/tools/network_approval_tests.rs | 5 +- .../runtimes/shell/unix_escalation_tests.rs | 31 ++--- .../core/tests/suite/request_permissions.rs | 109 +++++++++------ codex-rs/core/tests/suite/unified_exec.rs | 26 ++-- codex-rs/exec/tests/suite/sandbox.rs | 83 ++++++------ 15 files changed, 312 insertions(+), 376 deletions(-) diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index f3c56bfb3..c744eb3f6 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -138,7 +138,6 @@ use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::HookEventName; use codex_protocol::protocol::HookRunStatus; use codex_protocol::protocol::HookSource; -use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::SubAgentSource; use codex_protocol::protocol::ThreadSource; @@ -367,9 +366,7 @@ fn sample_turn_resolved_config(thread_id: &str, turn_id: &str) -> TurnResolvedCo session_source: SessionSource::Exec, model: "gpt-5".to_string(), model_provider: "openai".to_string(), - permission_profile: CorePermissionProfile::from_legacy_sandbox_policy( - &SandboxPolicy::new_read_only_policy(), - ), + permission_profile: CorePermissionProfile::read_only(), permission_profile_cwd: PathBuf::from("/tmp"), reasoning_effort: None, reasoning_summary: None, diff --git a/codex-rs/config/src/config_requirements.rs b/codex-rs/config/src/config_requirements.rs index 9faa84613..4e4336a2b 100644 --- a/codex-rs/config/src/config_requirements.rs +++ b/codex-rs/config/src/config_requirements.rs @@ -1255,8 +1255,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; @@ -1272,10 +1271,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, @@ -1963,9 +1958,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(), @@ -2110,9 +2103,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() ); @@ -2195,29 +2186,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(), @@ -2228,11 +2215,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(), @@ -2313,24 +2298,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(), @@ -2361,9 +2344,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(), @@ -2402,9 +2383,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/config_loader_tests.rs b/codex-rs/core/src/config/config_loader_tests.rs index 99da65b7c..cd84f06cd 100644 --- a/codex-rs/core/src/config/config_loader_tests.rs +++ b/codex-rs/core/src/config/config_loader_tests.rs @@ -29,7 +29,6 @@ use codex_protocol::config_types::TrustLevel; use codex_protocol::config_types::WebSearchMode; use codex_protocol::models::PermissionProfile; use codex_protocol::protocol::AskForApproval; -use codex_protocol::protocol::SandboxPolicy; use codex_utils_absolute_path::AbsolutePathBuf; use pretty_assertions::assert_eq; use std::collections::BTreeMap; @@ -821,6 +820,7 @@ flag = false async fn managed_preferences_expand_home_directory_in_workspace_write_roots() -> anyhow::Result<()> { use base64::Engine; + use codex_protocol::protocol::SandboxPolicy; let Some(home) = dirs::home_dir() else { return Ok(()); @@ -913,14 +913,7 @@ allowed_sandbox_modes = ["read-only"] state .requirements() .permission_profile - .can_set(&PermissionProfile::from_legacy_sandbox_policy( - &SandboxPolicy::WorkspaceWrite { - writable_roots: Vec::new(), - network_access: false, - exclude_tmpdir_env_var: false, - exclude_slash_tmp: false, - }, - )) + .can_set(&PermissionProfile::workspace_write()) .is_err() ); @@ -1233,11 +1226,9 @@ allowed_sandbox_modes = ["read-only"] let config_requirements: ConfigRequirements = config_requirements_toml.try_into()?; assert_eq!( - config_requirements.permission_profile.can_set( - &PermissionProfile::from_legacy_sandbox_policy( - &SandboxPolicy::new_workspace_write_policy() - ) - ), + config_requirements + .permission_profile + .can_set(&PermissionProfile::workspace_write()), Err(ConstraintError::InvalidValue { field_name: "sandbox_mode", candidate: "WorkspaceWrite".into(), @@ -1570,9 +1561,7 @@ async fn load_config_layers_applies_matching_remote_sandbox_config() -> anyhow:: layers .requirements() .permission_profile - .can_set(&PermissionProfile::from_legacy_sandbox_policy( - &SandboxPolicy::new_workspace_write_policy() - )) + .can_set(&PermissionProfile::workspace_write()) .is_ok() ); diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index b8bd7d239..ce7a36d93 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -4303,8 +4303,7 @@ fn web_search_mode_disabled_overrides_legacy_request() { #[test] fn web_search_mode_for_turn_uses_preference_for_read_only() { let web_search_mode = Constrained::allow_any(WebSearchMode::Cached); - let permission_profile = - PermissionProfile::from_legacy_sandbox_policy(&SandboxPolicy::new_read_only_policy()); + let permission_profile = PermissionProfile::read_only(); let mode = resolve_web_search_mode_for_turn(&web_search_mode, &permission_profile); assert_eq!(mode, WebSearchMode::Cached); @@ -8926,29 +8925,26 @@ async fn derive_sandbox_policy_preserves_windows_downgrade_for_unsupported_fallb let active_project = ProjectConfig { trust_level: Some(TrustLevel::Trusted), }; - let constrained = Constrained::new( - PermissionProfile::from_legacy_sandbox_policy(&SandboxPolicy::new_workspace_write_policy()), - |candidate| { - if matches!( - candidate, - PermissionProfile::Managed { - file_system: ManagedFileSystemPermissions::Restricted { entries, .. }, - .. - } if entries - .iter() - .any(|entry| entry.access.can_write()) - ) { - Ok(()) - } else { - Err(ConstraintError::InvalidValue { - field_name: "sandbox_mode", - candidate: format!("{candidate:?}"), - allowed: "[WorkspaceWrite]".to_string(), - requirement_source: RequirementSource::Unknown, - }) - } - }, - )?; + let constrained = Constrained::new(PermissionProfile::workspace_write(), |candidate| { + if matches!( + candidate, + PermissionProfile::Managed { + file_system: ManagedFileSystemPermissions::Restricted { entries, .. }, + .. + } if entries + .iter() + .any(|entry| entry.access.can_write()) + ) { + Ok(()) + } else { + Err(ConstraintError::InvalidValue { + field_name: "sandbox_mode", + candidate: format!("{candidate:?}"), + allowed: "[WorkspaceWrite]".to_string(), + requirement_source: RequirementSource::Unknown, + }) + } + })?; let resolution = derive_legacy_sandbox_policy_for_test( &cfg, diff --git a/codex-rs/core/src/exec_policy_tests.rs b/codex-rs/core/src/exec_policy_tests.rs index 8c424c541..7b0883db2 100644 --- a/codex-rs/core/src/exec_policy_tests.rs +++ b/codex-rs/core/src/exec_policy_tests.rs @@ -23,7 +23,6 @@ use codex_protocol::permissions::FileSystemSpecialPath; use codex_protocol::permissions::NetworkSandboxPolicy; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::GranularApprovalConfig; -use codex_protocol::protocol::SandboxPolicy; use codex_utils_absolute_path::AbsolutePathBuf; use pretty_assertions::assert_eq; use std::fs; @@ -130,10 +129,6 @@ fn external_file_system_sandbox_policy() -> FileSystemSandboxPolicy { FileSystemSandboxPolicy::external_sandbox() } -fn permission_profile_from_sandbox_policy(sandbox_policy: &SandboxPolicy) -> PermissionProfile { - PermissionProfile::from_legacy_sandbox_policy(sandbox_policy) -} - async fn test_config() -> (TempDir, Config) { let home = TempDir::new().expect("create temp dir"); let config = ConfigBuilder::without_managed_config_for_tests() @@ -669,7 +664,7 @@ async fn evaluates_bash_lc_inner_commands() { "rm -rf /some/important/folder".to_string(), ], approval_policy: AskForApproval::OnRequest, - sandbox_policy: SandboxPolicy::DangerFullAccess, + permission_profile: PermissionProfile::Disabled, file_system_sandbox_policy: unrestricted_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: None, @@ -765,7 +760,7 @@ async fn evaluates_heredoc_script_against_prefix_rules() { policy_src: Some(r#"prefix_rule(pattern=["python3"], decision="allow")"#.to_string()), command, approval_policy: AskForApproval::OnRequest, - sandbox_policy: SandboxPolicy::new_read_only_policy(), + permission_profile: PermissionProfile::read_only(), file_system_sandbox_policy: read_only_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: None, @@ -789,7 +784,7 @@ async fn omits_auto_amendment_for_heredoc_fallback_prompts() { "python3 <<'PY'\nprint('hello')\nPY".to_string(), ], approval_policy: AskForApproval::UnlessTrusted, - sandbox_policy: SandboxPolicy::new_read_only_policy(), + permission_profile: PermissionProfile::read_only(), file_system_sandbox_policy: read_only_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: None, @@ -813,7 +808,7 @@ async fn drops_requested_amendment_for_heredoc_fallback_prompts_when_it_wont_mat "python3 <<'PY'\nprint('hello')\nPY".to_string(), ], approval_policy: AskForApproval::UnlessTrusted, - sandbox_policy: SandboxPolicy::new_read_only_policy(), + permission_profile: PermissionProfile::read_only(), file_system_sandbox_policy: read_only_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: Some(vec![ @@ -841,7 +836,7 @@ async fn drops_requested_amendment_for_heredoc_fallback_prompts_when_it_matches( "python3 <<'PY'\nprint('hello')\nPY".to_string(), ], approval_policy: AskForApproval::UnlessTrusted, - sandbox_policy: SandboxPolicy::new_read_only_policy(), + permission_profile: PermissionProfile::read_only(), file_system_sandbox_policy: read_only_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: Some(vec!["python3".to_string()]), @@ -866,7 +861,7 @@ async fn heredoc_with_variable_assignment_is_not_reduced_to_allowed_prefix() { "PATH=/tmp/evil:$PATH cat <<'EOF'\nhello\nEOF".to_string(), ], approval_policy: AskForApproval::OnRequest, - sandbox_policy: SandboxPolicy::new_read_only_policy(), + permission_profile: PermissionProfile::read_only(), file_system_sandbox_policy: read_only_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: None, @@ -897,7 +892,7 @@ EOF"# .to_string(), ], approval_policy: AskForApproval::OnRequest, - sandbox_policy: SandboxPolicy::new_workspace_write_policy(), + permission_profile: PermissionProfile::workspace_write(), file_system_sandbox_policy: workspace_write_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: None, @@ -931,7 +926,7 @@ EOF"# .to_string(), ], approval_policy: AskForApproval::OnRequest, - sandbox_policy: SandboxPolicy::new_workspace_write_policy(), + permission_profile: PermissionProfile::workspace_write(), file_system_sandbox_policy: workspace_write_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::RequireEscalated, prefix_rule: None, @@ -971,7 +966,7 @@ prefix_rule( "/some/important/folder".to_string(), ], approval_policy: AskForApproval::OnRequest, - sandbox_policy: SandboxPolicy::DangerFullAccess, + permission_profile: PermissionProfile::Disabled, file_system_sandbox_policy: unrestricted_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: None, @@ -990,7 +985,7 @@ async fn exec_approval_requirement_prefers_execpolicy_match() { policy_src: Some(r#"prefix_rule(pattern=["rm"], decision="prompt")"#.to_string()), command: vec!["rm".to_string()], approval_policy: AskForApproval::OnRequest, - sandbox_policy: SandboxPolicy::DangerFullAccess, + permission_profile: PermissionProfile::Disabled, file_system_sandbox_policy: unrestricted_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: None, @@ -1018,7 +1013,7 @@ prefix_rule(pattern=["git"], decision="allow") policy_src: Some(policy_src), command: vec![git_path, "status".to_string()], approval_policy: AskForApproval::UnlessTrusted, - sandbox_policy: SandboxPolicy::new_read_only_policy(), + permission_profile: PermissionProfile::read_only(), file_system_sandbox_policy: read_only_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: None, @@ -1052,7 +1047,7 @@ prefix_rule(pattern=["git"], decision="prompt") policy_src: Some(policy_src), command: vec![disallowed_git_path.clone(), "status".to_string()], approval_policy: AskForApproval::UnlessTrusted, - sandbox_policy: SandboxPolicy::new_read_only_policy(), + permission_profile: PermissionProfile::read_only(), file_system_sandbox_policy: read_only_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: None, @@ -1079,7 +1074,7 @@ async fn requested_prefix_rule_can_approve_absolute_path_commands() { "cargo-insta".to_string(), ], approval_policy: AskForApproval::UnlessTrusted, - sandbox_policy: SandboxPolicy::new_read_only_policy(), + permission_profile: PermissionProfile::read_only(), file_system_sandbox_policy: read_only_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: Some(vec!["cargo".to_string(), "install".to_string()]), @@ -1102,7 +1097,7 @@ async fn exec_approval_requirement_respects_approval_policy() { policy_src: Some(r#"prefix_rule(pattern=["rm"], decision="prompt")"#.to_string()), command: vec!["rm".to_string()], approval_policy: AskForApproval::Never, - sandbox_policy: SandboxPolicy::DangerFullAccess, + permission_profile: PermissionProfile::Disabled, file_system_sandbox_policy: unrestricted_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: None, @@ -1130,9 +1125,7 @@ fn unmatched_granular_policy_still_prompts_for_restricted_sandbox_escalation() { request_permissions: true, mcp_elicitations: true, }), - 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: Path::new("/tmp"), sandbox_permissions: SandboxPermissions::RequireEscalated, @@ -1175,9 +1168,7 @@ fn known_safe_on_request_still_prompts_for_restricted_sandbox_escalation() { &command, UnmatchedCommandContext { approval_policy: AskForApproval::OnRequest, - permission_profile: &permission_profile_from_sandbox_policy( - &SandboxPolicy::new_workspace_write_policy(), - ), + permission_profile: &PermissionProfile::workspace_write(), file_system_sandbox_policy: &workspace_write_file_system_sandbox_policy(), sandbox_cwd: Path::new("/tmp"), sandbox_permissions: SandboxPermissions::RequireEscalated, @@ -1258,7 +1249,7 @@ async fn exec_approval_requirement_prompts_for_inline_additional_permissions_und "touch requested-dir/requested-but-unused.txt".to_string(), ], approval_policy: AskForApproval::OnRequest, - sandbox_policy: SandboxPolicy::new_read_only_policy(), + permission_profile: PermissionProfile::read_only(), file_system_sandbox_policy: read_only_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::WithAdditionalPermissions, prefix_rule: None, @@ -1281,7 +1272,7 @@ async fn exec_approval_requirement_prompts_for_known_safe_escalation_under_on_re policy_src: None, command: vec!["echo".to_string(), "hello".to_string()], approval_policy: AskForApproval::OnRequest, - sandbox_policy: SandboxPolicy::new_workspace_write_policy(), + permission_profile: PermissionProfile::workspace_write(), file_system_sandbox_policy: workspace_write_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::RequireEscalated, prefix_rule: None, @@ -1311,7 +1302,7 @@ async fn exec_approval_requirement_rejects_known_safe_escalation_when_granular_s request_permissions: true, mcp_elicitations: true, }), - sandbox_policy: SandboxPolicy::new_workspace_write_policy(), + permission_profile: PermissionProfile::workspace_write(), file_system_sandbox_policy: workspace_write_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::RequireEscalated, prefix_rule: None, @@ -1337,7 +1328,7 @@ async fn exec_approval_requirement_rejects_unmatched_sandbox_escalation_when_gra request_permissions: true, mcp_elicitations: true, }), - sandbox_policy: SandboxPolicy::new_read_only_policy(), + permission_profile: PermissionProfile::read_only(), file_system_sandbox_policy: read_only_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::RequireEscalated, prefix_rule: None, @@ -1373,9 +1364,7 @@ async fn mixed_rule_and_sandbox_prompt_prioritizes_rule_for_rejection_decision() request_permissions: true, mcp_elicitations: true, }), - 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: Path::new("/tmp"), sandbox_permissions: SandboxPermissions::RequireEscalated, @@ -1413,9 +1402,7 @@ async fn mixed_rule_and_sandbox_prompt_rejects_when_granular_rules_are_disabled( request_permissions: true, mcp_elicitations: true, }), - 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: Path::new("/tmp"), sandbox_permissions: SandboxPermissions::RequireEscalated, @@ -1440,9 +1427,7 @@ async fn exec_approval_requirement_falls_back_to_heuristics() { .create_exec_approval_requirement_for_command(ExecApprovalRequest { command: &command, approval_policy: AskForApproval::UnlessTrusted, - 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: Path::new("/tmp"), sandbox_permissions: SandboxPermissions::UseDefault, @@ -1468,9 +1453,7 @@ async fn empty_bash_lc_script_falls_back_to_original_command() { .create_exec_approval_requirement_for_command(ExecApprovalRequest { command: &command, approval_policy: AskForApproval::UnlessTrusted, - 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: Path::new("/tmp"), sandbox_permissions: SandboxPermissions::UseDefault, @@ -1500,9 +1483,7 @@ async fn whitespace_bash_lc_script_falls_back_to_original_command() { .create_exec_approval_requirement_for_command(ExecApprovalRequest { command: &command, approval_policy: AskForApproval::UnlessTrusted, - 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: Path::new("/tmp"), sandbox_permissions: SandboxPermissions::UseDefault, @@ -1532,9 +1513,7 @@ async fn request_rule_uses_prefix_rule() { .create_exec_approval_requirement_for_command(ExecApprovalRequest { command: &command, 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: Path::new("/tmp"), sandbox_permissions: SandboxPermissions::RequireEscalated, @@ -1683,7 +1662,7 @@ async fn proposed_execpolicy_amendment_is_present_for_single_command_without_pol policy_src: None, command: command.clone(), approval_policy: AskForApproval::UnlessTrusted, - sandbox_policy: SandboxPolicy::new_read_only_policy(), + permission_profile: PermissionProfile::read_only(), file_system_sandbox_policy: read_only_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: None, @@ -1703,7 +1682,7 @@ async fn proposed_execpolicy_amendment_is_omitted_when_policy_prompts() { policy_src: Some(r#"prefix_rule(pattern=["rm"], decision="prompt")"#.to_string()), command: vec!["rm".to_string()], approval_policy: AskForApproval::OnRequest, - sandbox_policy: SandboxPolicy::DangerFullAccess, + permission_profile: PermissionProfile::Disabled, file_system_sandbox_policy: unrestricted_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: None, @@ -1727,7 +1706,7 @@ async fn proposed_execpolicy_amendment_is_present_for_multi_command_scripts() { "cargo build && echo ok".to_string(), ], approval_policy: AskForApproval::UnlessTrusted, - sandbox_policy: SandboxPolicy::new_read_only_policy(), + permission_profile: PermissionProfile::read_only(), file_system_sandbox_policy: read_only_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: None, @@ -1757,7 +1736,7 @@ async fn proposed_execpolicy_amendment_uses_first_no_match_in_multi_command_scri policy_src: Some(policy_src.to_string()), command, approval_policy: AskForApproval::UnlessTrusted, - sandbox_policy: SandboxPolicy::new_read_only_policy(), + permission_profile: PermissionProfile::read_only(), file_system_sandbox_policy: read_only_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: None, @@ -1781,7 +1760,7 @@ async fn proposed_execpolicy_amendment_is_present_when_heuristics_allow() { policy_src: None, command: command.clone(), approval_policy: AskForApproval::OnRequest, - sandbox_policy: SandboxPolicy::new_read_only_policy(), + permission_profile: PermissionProfile::read_only(), file_system_sandbox_policy: read_only_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: None, @@ -1805,7 +1784,7 @@ async fn proposed_execpolicy_amendment_is_suppressed_when_policy_matches_allow() "print(1)".to_string(), ], approval_policy: AskForApproval::OnRequest, - sandbox_policy: SandboxPolicy::new_read_only_policy(), + permission_profile: PermissionProfile::read_only(), file_system_sandbox_policy: read_only_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: None, @@ -1836,7 +1815,7 @@ prefix_rule(pattern=["cat"], decision="allow") policy_src: Some(policy_src.to_string()), command: command.clone(), approval_policy, - sandbox_policy: SandboxPolicy::new_workspace_write_policy(), + permission_profile: PermissionProfile::workspace_write(), file_system_sandbox_policy: workspace_write_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: None, @@ -1868,7 +1847,7 @@ prefix_rule(pattern=["bash"], decision="allow") .to_string(), ], approval_policy: AskForApproval::OnRequest, - sandbox_policy: SandboxPolicy::new_read_only_policy(), + permission_profile: PermissionProfile::read_only(), file_system_sandbox_policy: read_only_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: None, @@ -2034,7 +2013,7 @@ async fn dangerous_rm_rf_requires_approval_in_danger_full_access() { policy_src: None, command: command.clone(), approval_policy: AskForApproval::OnRequest, - sandbox_policy: SandboxPolicy::DangerFullAccess, + permission_profile: PermissionProfile::Disabled, file_system_sandbox_policy: unrestricted_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: None, @@ -2098,9 +2077,7 @@ async fn verify_approval_requirement_for_unsafe_powershell_command() { .create_exec_approval_requirement_for_command(ExecApprovalRequest { command: &sneaky_command, 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: Path::new("/tmp"), sandbox_permissions: permissions, @@ -2125,9 +2102,7 @@ async fn verify_approval_requirement_for_unsafe_powershell_command() { .create_exec_approval_requirement_for_command(ExecApprovalRequest { command: &dangerous_command, 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: Path::new("/tmp"), sandbox_permissions: permissions, @@ -2148,9 +2123,7 @@ async fn verify_approval_requirement_for_unsafe_powershell_command() { .create_exec_approval_requirement_for_command(ExecApprovalRequest { command: &dangerous_command, approval_policy: AskForApproval::Never, - 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: Path::new("/tmp"), sandbox_permissions: permissions, @@ -2170,8 +2143,8 @@ async fn dangerous_command_allowed_when_sandbox_is_explicitly_disabled() { policy_src: None, command, approval_policy: AskForApproval::Never, - sandbox_policy: SandboxPolicy::ExternalSandbox { - network_access: Default::default(), + permission_profile: PermissionProfile::External { + network: NetworkSandboxPolicy::Restricted, }, file_system_sandbox_policy: external_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, @@ -2195,8 +2168,8 @@ async fn dangerous_command_forbidden_in_external_sandbox_when_policy_matches() { policy_src: Some("prefix_rule(pattern=['rm'], decision='prompt')".to_string()), command, approval_policy: AskForApproval::Never, - sandbox_policy: SandboxPolicy::ExternalSandbox { - network_access: Default::default(), + permission_profile: PermissionProfile::External { + network: NetworkSandboxPolicy::Restricted, }, file_system_sandbox_policy: external_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, @@ -2214,7 +2187,7 @@ struct ExecApprovalRequirementScenario { policy_src: Option, command: Vec, approval_policy: AskForApproval, - sandbox_policy: SandboxPolicy, + permission_profile: PermissionProfile, file_system_sandbox_policy: FileSystemSandboxPolicy, sandbox_permissions: SandboxPermissions, prefix_rule: Option>, @@ -2238,7 +2211,7 @@ async fn exec_approval_requirement_for_command( policy_src, command, approval_policy, - sandbox_policy, + permission_profile, file_system_sandbox_policy, sandbox_permissions, prefix_rule, @@ -2246,7 +2219,6 @@ async fn exec_approval_requirement_for_command( let policy = policy_from_src(policy_src.as_deref()); - let permission_profile = permission_profile_from_sandbox_policy(&sandbox_policy); ExecPolicyManager::new(policy) .create_exec_approval_requirement_for_command(ExecApprovalRequest { command: &command, diff --git a/codex-rs/core/src/exec_policy_windows_tests.rs b/codex-rs/core/src/exec_policy_windows_tests.rs index c1552f7e1..1d14d9381 100644 --- a/codex-rs/core/src/exec_policy_windows_tests.rs +++ b/codex-rs/core/src/exec_policy_windows_tests.rs @@ -14,7 +14,7 @@ async fn evaluates_powershell_inner_commands_against_prompt_rules() { "echo blocked".to_string(), ], approval_policy: AskForApproval::Never, - sandbox_policy: SandboxPolicy::DangerFullAccess, + permission_profile: PermissionProfile::Disabled, file_system_sandbox_policy: unrestricted_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: None, @@ -38,7 +38,7 @@ async fn evaluates_powershell_inner_commands_against_allow_rules() { "echo blocked".to_string(), ], approval_policy: AskForApproval::UnlessTrusted, - sandbox_policy: SandboxPolicy::new_read_only_policy(), + permission_profile: PermissionProfile::read_only(), file_system_sandbox_policy: read_only_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: None, @@ -80,9 +80,7 @@ fn unmatched_safe_powershell_words_are_allowed() { &command, UnmatchedCommandContext { approval_policy: AskForApproval::UnlessTrusted, - 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: Path::new("/tmp"), sandbox_permissions: SandboxPermissions::UseDefault, @@ -111,7 +109,7 @@ async fn unmatched_dangerous_powershell_inner_commands_require_approval() { "Remove-Item test -Force".to_string(), ], approval_policy: AskForApproval::OnRequest, - sandbox_policy: SandboxPolicy::DangerFullAccess, + permission_profile: PermissionProfile::Disabled, file_system_sandbox_policy: unrestricted_file_system_sandbox_policy(), sandbox_permissions: SandboxPermissions::UseDefault, prefix_rule: None, diff --git a/codex-rs/core/src/exec_tests.rs b/codex-rs/core/src/exec_tests.rs index 1e802121a..268cbae97 100644 --- a/codex-rs/core/src/exec_tests.rs +++ b/codex-rs/core/src/exec_tests.rs @@ -347,8 +347,7 @@ async fn process_exec_tool_call_preserves_full_buffer_capture_policy() -> Result ]; let cwd = codex_utils_absolute_path::AbsolutePathBuf::current_dir()?; - let sandbox_policy = SandboxPolicy::DangerFullAccess; - let permission_profile = PermissionProfile::from_legacy_sandbox_policy(&sandbox_policy); + let permission_profile = PermissionProfile::Disabled; let output = process_exec_tool_call( ExecParams { command, diff --git a/codex-rs/core/src/guardian/tests.rs b/codex-rs/core/src/guardian/tests.rs index 742b3c8ae..0544b2777 100644 --- a/codex-rs/core/src/guardian/tests.rs +++ b/codex-rs/core/src/guardian/tests.rs @@ -42,7 +42,6 @@ use codex_protocol::protocol::GuardianRiskLevel; use codex_protocol::protocol::GuardianUserAuthorization; use codex_protocol::protocol::ReviewDecision; use codex_protocol::protocol::RolloutItem; -use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::TurnCompleteEvent; use core_test_support::PathBufExt; use core_test_support::TempDirExt; @@ -2191,7 +2190,7 @@ async fn guardian_review_session_config_preserves_parent_network_proxy() { ); assert_eq!( guardian_config.permissions.permission_profile(), - &PermissionProfile::from_legacy_sandbox_policy(&SandboxPolicy::new_read_only_policy()) + &PermissionProfile::read_only() ); } @@ -2253,9 +2252,7 @@ async fn guardian_review_session_config_uses_live_network_proxy_state() { NetworkProxySpec::from_config_and_constraints( live_network, /*requirements*/ None, - &PermissionProfile::from_legacy_sandbox_policy( - &SandboxPolicy::new_read_only_policy(), - ), + &PermissionProfile::read_only(), ) .expect("live network proxy spec") ) diff --git a/codex-rs/core/src/safety_tests.rs b/codex-rs/core/src/safety_tests.rs index d69917249..ce51d8c43 100644 --- a/codex-rs/core/src/safety_tests.rs +++ b/codex-rs/core/src/safety_tests.rs @@ -1,20 +1,16 @@ use super::*; use codex_protocol::models::PermissionProfile; +use codex_protocol::permissions::NetworkSandboxPolicy; use codex_protocol::protocol::FileSystemAccessMode; use codex_protocol::protocol::FileSystemPath; use codex_protocol::protocol::FileSystemSandboxEntry; use codex_protocol::protocol::FileSystemSpecialPath; 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) -} - #[test] fn test_writable_roots_constraint() { // Use a temporary directory as our workspace to avoid touching @@ -32,36 +28,34 @@ fn test_writable_roots_constraint() { // 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, - }; + let workspace_only_file_system_policy = FileSystemSandboxPolicy::workspace_write( + &[], + /*exclude_tmpdir_env_var*/ true, + /*exclude_slash_tmp*/ true, + ); 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 file_system_policy_with_parent = FileSystemSandboxPolicy::workspace_write( + std::slice::from_ref(&parent), + /*exclude_tmpdir_env_var*/ true, + /*exclude_slash_tmp*/ true, + ); assert!(is_write_patch_constrained_to_writable_paths( &add_outside, - &FileSystemSandboxPolicy::from(&policy_with_parent), + &file_system_policy_with_parent, &cwd, )); } @@ -73,16 +67,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 = FileSystemSandboxPolicy::external_sandbox(); 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 +95,20 @@ 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 = PermissionProfile::workspace_write_with( + &[], + NetworkSandboxPolicy::Restricted, + /*exclude_tmpdir_env_var*/ true, + /*exclude_slash_tmp*/ true, + ); + let file_system_sandbox_policy = permission_profile.file_system_sandbox_policy(); 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 +124,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 +140,13 @@ 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 = PermissionProfile::workspace_write_with( + &[], + NetworkSandboxPolicy::Restricted, + /*exclude_tmpdir_env_var*/ true, + /*exclude_slash_tmp*/ true, + ); + let file_system_sandbox_policy = permission_profile.file_system_sandbox_policy(); assert_eq!( assess_patch_safety( @@ -161,8 +158,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 +175,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 = permission_profile.file_system_sandbox_policy(); assert!(!is_write_patch_constrained_to_writable_paths( &action, @@ -191,7 +187,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 +204,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 +231,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 +248,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 +275,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 +290,21 @@ 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 = PermissionProfile::workspace_write_with( + &[], + NetworkSandboxPolicy::Restricted, + /*exclude_tmpdir_env_var*/ true, + /*exclude_slash_tmp*/ true, + ); + let mut file_system_sandbox_policy = permission_profile.file_system_sandbox_policy(); + file_system_sandbox_policy + .entries + .push(FileSystemSandboxEntry { + path: FileSystemPath::Path { + path: cwd.join(".codex"), + }, + access: FileSystemAccessMode::Read, + }); assert!(!is_write_patch_constrained_to_writable_paths( &action, @@ -312,7 +315,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, diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index 18277f468..bb858999e 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -816,7 +816,6 @@ async fn managed_network_proxy_decider_survives_full_access_start() -> anyhow::R async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow::Result<()> { let (mut session, _turn_context) = make_session_and_context().await; let initial_permission_profile = PermissionProfile::workspace_write(); - let initial_policy = SandboxPolicy::new_workspace_write_policy(); let mut network_config = NetworkProxyConfig::default(); network_config @@ -860,11 +859,10 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow let mut state = session.state.lock().await; let mut config = (*state.session_configuration.original_config_do_not_use).clone(); config.permissions.network = Some(spec); - let cwd = config.cwd.clone(); config .permissions - .set_legacy_sandbox_policy(initial_policy.clone(), cwd.as_path()) - .expect("test setup should allow sandbox policy"); + .set_permission_profile(initial_permission_profile.clone()) + .expect("test setup should allow permission profile"); state.session_configuration.original_config_do_not_use = Arc::new(config); state .session_configuration @@ -913,11 +911,10 @@ async fn danger_full_access_turns_do_not_expose_managed_network_proxy() -> anyho )?; let session = make_session_with_config(move |config| { - let cwd = config.cwd.clone(); config .permissions - .set_legacy_sandbox_policy(SandboxPolicy::DangerFullAccess, cwd.as_path()) - .expect("test setup should allow sandbox policy"); + .set_permission_profile(PermissionProfile::Disabled) + .expect("test setup should allow permission profile"); config.permissions.network = Some(network_spec); }) .await?; @@ -979,11 +976,10 @@ async fn danger_full_access_tool_attempts_do_not_enforce_managed_network() -> an )?; let session = make_session_with_config(move |config| { - let cwd = config.cwd.clone(); config .permissions - .set_legacy_sandbox_policy(SandboxPolicy::DangerFullAccess, cwd.as_path()) - .expect("test setup should allow sandbox policy"); + .set_permission_profile(PermissionProfile::Disabled) + .expect("test setup should allow permission profile"); config.permissions.network = Some(network_spec); let layers = config @@ -1044,7 +1040,6 @@ async fn danger_full_access_tool_attempts_do_not_enforce_managed_network() -> an #[tokio::test] async fn workspace_write_turns_continue_to_expose_managed_network_proxy() -> anyhow::Result<()> { let permission_profile = PermissionProfile::workspace_write(); - let sandbox_policy = SandboxPolicy::new_workspace_write_policy(); let network_spec = crate::config::NetworkProxySpec::from_config_and_constraints( NetworkProxyConfig::default(), Some(NetworkConstraints { @@ -1055,11 +1050,10 @@ async fn workspace_write_turns_continue_to_expose_managed_network_proxy() -> any )?; let session = make_session_with_config(move |config| { - let cwd = config.cwd.clone(); config .permissions - .set_legacy_sandbox_policy(sandbox_policy, cwd.as_path()) - .expect("test setup should allow sandbox policy"); + .set_permission_profile(permission_profile) + .expect("test setup should allow permission profile"); config.permissions.network = Some(network_spec); }) .await?; @@ -1072,7 +1066,6 @@ async fn workspace_write_turns_continue_to_expose_managed_network_proxy() -> any #[tokio::test] async fn user_shell_commands_do_not_inherit_managed_network_proxy() -> anyhow::Result<()> { let permission_profile = PermissionProfile::workspace_write(); - let sandbox_policy = SandboxPolicy::new_workspace_write_policy(); let network_spec = crate::config::NetworkProxySpec::from_config_and_constraints( NetworkProxyConfig::default(), Some(NetworkConstraints { @@ -1083,11 +1076,10 @@ async fn user_shell_commands_do_not_inherit_managed_network_proxy() -> anyhow::R )?; let (session, rx) = make_session_with_config_and_rx(move |config| { - let cwd = config.cwd.clone(); config .permissions - .set_legacy_sandbox_policy(sandbox_policy, cwd.as_path()) - .expect("test setup should allow sandbox policy"); + .set_permission_profile(permission_profile) + .expect("test setup should allow permission profile"); config.permissions.network = Some(network_spec); }) .await?; diff --git a/codex-rs/core/src/tools/network_approval_tests.rs b/codex-rs/core/src/tools/network_approval_tests.rs index a98d65068..25bcfe255 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/shell/unix_escalation_tests.rs b/codex-rs/core/src/tools/runtimes/shell/unix_escalation_tests.rs index 7c2aa5e8e..e8a14083f 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() } @@ -429,9 +424,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, @@ -498,9 +491,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, @@ -553,9 +544,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, @@ -599,9 +588,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, @@ -633,7 +620,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(); @@ -643,7 +630,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( @@ -659,7 +646,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, @@ -694,9 +681,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, diff --git a/codex-rs/core/tests/suite/request_permissions.rs b/codex-rs/core/tests/suite/request_permissions.rs index 455c1fabb..0463ea3e2 100644 --- a/codex-rs/core/tests/suite/request_permissions.rs +++ b/codex-rs/core/tests/suite/request_permissions.rs @@ -7,6 +7,8 @@ use codex_features::Feature; use codex_protocol::config_types::ApprovalsReviewer; use codex_protocol::models::AdditionalPermissionProfile as PermissionProfile; use codex_protocol::models::FileSystemPermissions; +use codex_protocol::models::PermissionProfile as CorePermissionProfile; +use codex_protocol::permissions::NetworkSandboxPolicy; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::ExecApprovalRequestEvent; @@ -292,6 +294,15 @@ fn workspace_write_excluding_tmp() -> SandboxPolicy { } } +fn workspace_write_excluding_tmp_profile() -> CorePermissionProfile { + CorePermissionProfile::workspace_write_with( + &[], + NetworkSandboxPolicy::Restricted, + /*exclude_tmpdir_env_var*/ true, + /*exclude_slash_tmp*/ true, + ) +} + fn requested_directory_write_permissions(path: &Path) -> RequestPermissionProfile { RequestPermissionProfile { file_system: Some(FileSystemPermissions::from_read_write_roots( @@ -320,13 +331,14 @@ async fn with_additional_permissions_requires_approval_under_on_request() -> Res let server = start_mock_server().await; let approval_policy = AskForApproval::OnRequest; let sandbox_policy = SandboxPolicy::new_read_only_policy(); - let sandbox_policy_for_config = sandbox_policy.clone(); + let permission_profile_for_config = CorePermissionProfile::read_only(); 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) @@ -417,13 +429,14 @@ async fn request_permissions_tool_is_auto_denied_when_granular_request_permissio mcp_elicitations: true, }); let sandbox_policy = SandboxPolicy::new_read_only_policy(); - let sandbox_policy_for_config = sandbox_policy.clone(); + let permission_profile_for_config = CorePermissionProfile::read_only(); 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::RequestPermissionsTool) @@ -502,13 +515,14 @@ async fn relative_additional_permissions_resolve_against_tool_workdir() -> Resul let server = start_mock_server().await; let approval_policy = AskForApproval::OnRequest; let sandbox_policy = SandboxPolicy::new_read_only_policy(); - let sandbox_policy_for_config = sandbox_policy.clone(); + let permission_profile_for_config = CorePermissionProfile::read_only(); 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) @@ -605,13 +619,14 @@ async fn read_only_with_additional_permissions_does_not_widen_to_unrequested_cwd let server = start_mock_server().await; let approval_policy = AskForApproval::OnRequest; let sandbox_policy = SandboxPolicy::new_read_only_policy(); - let sandbox_policy_for_config = sandbox_policy.clone(); + let permission_profile_for_config = CorePermissionProfile::read_only(); 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) @@ -707,13 +722,14 @@ async fn read_only_with_additional_permissions_does_not_widen_to_unrequested_tmp let server = start_mock_server().await; let approval_policy = AskForApproval::OnRequest; let sandbox_policy = SandboxPolicy::new_read_only_policy(); - let sandbox_policy_for_config = sandbox_policy.clone(); + let permission_profile_for_config = CorePermissionProfile::read_only(); 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) @@ -808,13 +824,14 @@ async fn workspace_write_with_additional_permissions_can_write_outside_cwd() -> 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_for_config = workspace_write_excluding_tmp_profile(); 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) @@ -914,13 +931,14 @@ async fn with_additional_permissions_denied_approval_blocks_execution() -> Resul 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_for_config = workspace_write_excluding_tmp_profile(); 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) @@ -1021,13 +1039,14 @@ async fn request_permissions_grants_apply_to_later_exec_command_calls() -> Resul 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_for_config = workspace_write_excluding_tmp_profile(); 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) @@ -1147,13 +1166,14 @@ async fn request_permissions_preapprove_explicit_exec_permissions_outside_on_req 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_for_config = workspace_write_excluding_tmp_profile(); 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) @@ -1267,13 +1287,14 @@ async fn request_permissions_grants_apply_to_later_shell_command_calls() -> Resu 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_for_config = workspace_write_excluding_tmp_profile(); 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) @@ -1381,13 +1402,14 @@ async fn request_permissions_grants_apply_to_later_shell_command_calls_without_i 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_for_config = workspace_write_excluding_tmp_profile(); 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::RequestPermissionsTool) @@ -1495,13 +1517,14 @@ async fn partial_request_permissions_grants_do_not_preapprove_new_permissions() 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_for_config = workspace_write_excluding_tmp_profile(); 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) @@ -1661,13 +1684,14 @@ async fn request_permissions_grants_do_not_carry_across_turns() -> 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_for_config = workspace_write_excluding_tmp_profile(); 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) @@ -1776,13 +1800,14 @@ async fn request_permissions_session_grants_carry_across_turns() -> 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_for_config = workspace_write_excluding_tmp_profile(); 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) diff --git a/codex-rs/core/tests/suite/unified_exec.rs b/codex-rs/core/tests/suite/unified_exec.rs index 8687b33ff..92b8aa904 100644 --- a/codex-rs/core/tests/suite/unified_exec.rs +++ b/codex-rs/core/tests/suite/unified_exec.rs @@ -9,6 +9,7 @@ use codex_exec_server::CreateDirectoryOptions; use codex_features::Feature; use codex_protocol::models::PermissionProfile; use codex_protocol::models::ResponseItem; +use codex_protocol::permissions::NetworkSandboxPolicy; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::ExecCommandSource; @@ -890,11 +891,16 @@ mode = "limited" allow_local_binding = true "#, )?; - let mut sandbox_policy = SandboxPolicy::new_workspace_write_policy(); - if let SandboxPolicy::WorkspaceWrite { network_access, .. } = &mut sandbox_policy { - *network_access = true; - } - let sandbox_policy_for_config = sandbox_policy.clone(); + let permission_profile_for_config = PermissionProfile::workspace_write_with( + &[], + NetworkSandboxPolicy::Enabled, + /*exclude_tmpdir_env_var*/ false, + /*exclude_slash_tmp*/ false, + ); + let sandbox_policy = permission_profile_for_config + .clone() + .to_legacy_sandbox_policy(home.path()) + .expect("workspace-write profile should project to legacy policy"); let mut builder = test_codex() .with_home(home) .with_cloud_requirements(managed_network_requirements_loader()) @@ -907,9 +913,7 @@ allow_local_binding = true config.permissions.approval_policy = Constrained::allow_any(AskForApproval::Never); config .permissions - .set_permission_profile(PermissionProfile::from_legacy_sandbox_policy( - &sandbox_policy_for_config, - )) + .set_permission_profile(permission_profile_for_config) .expect("set permission profile"); }); let test = builder.build_with_remote_env(server).await?; @@ -2734,16 +2738,11 @@ async fn unified_exec_enforces_glob_deny_read_policy() -> Result<()> { skip_if_sandbox!(Ok(())); let server = start_mock_server().await; - let read_only_policy = SandboxPolicy::new_read_only_policy(); - let read_only_policy_for_config = read_only_policy.clone(); let mut builder = test_codex().with_config(move |config| { config .features .enable(Feature::UnifiedExec) .expect("test config should allow feature update"); - config - .set_legacy_sandbox_policy(read_only_policy_for_config) - .expect("set sandbox policy"); let mut file_system_sandbox_policy = FileSystemSandboxPolicy::default(); file_system_sandbox_policy .entries @@ -2800,6 +2799,7 @@ async fn unified_exec_enforces_glob_deny_read_policy() -> Result<()> { let request_log = mount_sse_sequence(&server, responses).await; let session_model = session_configured.model.clone(); + let read_only_policy = SandboxPolicy::new_read_only_policy(); codex .submit(Op::UserTurn { environments: None, diff --git a/codex-rs/exec/tests/suite/sandbox.rs b/codex-rs/exec/tests/suite/sandbox.rs index 8f8eac323..0a5381b1e 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: true, - exclude_slash_tmp: true, - }; + let permission_profile = PermissionProfile::workspace_write_with( + &writable_roots, + NetworkSandboxPolicy::Restricted, + /*exclude_tmpdir_env_var*/ true, + /*exclude_slash_tmp*/ true, + ); 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())]),