From 20ba4bf492337297f9f9e8c228610fec802f5a6e Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Sun, 26 Apr 2026 20:31:32 -0700 Subject: [PATCH] permissions: store only constrained permission profiles --- codex-rs/core/src/agent/role_tests.rs | 2 +- .../core/src/config/config_loader_tests.rs | 2 +- codex-rs/core/src/config/config_tests.rs | 59 ++++++++----------- codex-rs/core/src/config/mod.rs | 36 +++++------ codex-rs/core/src/guardian/review_session.rs | 1 - codex-rs/core/src/guardian/tests.rs | 6 +- codex-rs/core/src/memories/tests.rs | 7 ++- codex-rs/core/src/session/tests.rs | 6 +- codex-rs/core/src/session/turn_context.rs | 2 - .../src/tools/handlers/multi_agents_common.rs | 2 +- .../src/tools/handlers/multi_agents_tests.rs | 33 ++++++++--- codex-rs/core/tests/common/zsh_fork.rs | 4 +- codex-rs/core/tests/suite/agent_websocket.rs | 28 +++------ codex-rs/core/tests/suite/approvals.rs | 32 +++++++--- codex-rs/core/tests/suite/client.rs | 4 +- codex-rs/core/tests/suite/codex_delegate.rs | 10 ++-- .../tests/suite/collaboration_instructions.rs | 4 +- codex-rs/core/tests/suite/hooks.rs | 4 +- codex-rs/core/tests/suite/otel.rs | 5 +- codex-rs/core/tests/suite/prompt_caching.rs | 4 +- codex-rs/core/tests/suite/remote_models.rs | 10 ++-- .../core/tests/suite/request_permissions.rs | 56 +++++++++++++----- .../tests/suite/request_permissions_tool.rs | 8 ++- codex-rs/core/tests/suite/resume_warning.rs | 2 +- codex-rs/core/tests/suite/tools.rs | 12 ++-- codex-rs/core/tests/suite/unified_exec.rs | 4 +- codex-rs/tui/src/app/tests.rs | 23 ++------ codex-rs/tui/src/app/thread_session_state.rs | 5 +- codex-rs/tui/src/chatwidget.rs | 19 ++++-- .../src/chatwidget/tests/history_replay.rs | 8 +-- .../tui/src/chatwidget/tests/permissions.rs | 27 +++++---- codex-rs/tui/src/status/tests.rs | 32 ++++------ 32 files changed, 242 insertions(+), 215 deletions(-) diff --git a/codex-rs/core/src/agent/role_tests.rs b/codex-rs/core/src/agent/role_tests.rs index d8b277db9953..eceaaa92006c 100644 --- a/codex-rs/core/src/agent/role_tests.rs +++ b/codex-rs/core/src/agent/role_tests.rs @@ -574,7 +574,7 @@ writable_roots = ["./sandbox-root"] false ); - match &*config.permissions.sandbox_policy { + match &config.legacy_sandbox_policy() { SandboxPolicy::WorkspaceWrite { network_access, .. } => { assert_eq!(*network_access, true); } diff --git a/codex-rs/core/src/config/config_loader_tests.rs b/codex-rs/core/src/config/config_loader_tests.rs index 5505cf2bafa3..3a77c1618968 100644 --- a/codex-rs/core/src/config/config_loader_tests.rs +++ b/codex-rs/core/src/config/config_loader_tests.rs @@ -526,7 +526,7 @@ writable_roots = ["~/code"] .await?; let expected_root = AbsolutePathBuf::from_absolute_path(home.join("code"))?; - match config.permissions.sandbox_policy.get() { + match &config.legacy_sandbox_policy() { SandboxPolicy::WorkspaceWrite { writable_roots, .. } => { assert_eq!( writable_roots diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index c9a8b8818eb5..a55e444e994c 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -806,7 +806,7 @@ async fn default_permissions_profile_populates_runtime_sandbox_policy() -> std:: ]), ); assert_eq!( - config.permissions.sandbox_policy.get(), + &config.legacy_sandbox_policy(), &SandboxPolicy::WorkspaceWrite { writable_roots: vec![memories_root], network_access: false, @@ -840,7 +840,7 @@ async fn permission_profile_override_populates_runtime_permissions() -> std::io: assert_eq!(config.permissions.permission_profile(), permission_profile); assert_eq!( - config.permissions.sandbox_policy.get(), + &config.legacy_sandbox_policy(), &SandboxPolicy::DangerFullAccess ); Ok(()) @@ -869,7 +869,7 @@ async fn permission_profile_override_preserves_managed_unrestricted_filesystem() assert_eq!(config.permissions.permission_profile(), permission_profile); assert_eq!( - config.permissions.sandbox_policy.get(), + &config.legacy_sandbox_policy(), &SandboxPolicy::ExternalSandbox { network_access: NetworkAccess::Restricted, } @@ -898,7 +898,7 @@ async fn managed_unrestricted_permission_profile_still_enables_network_requireme ) .await?; assert_eq!( - config.permissions.sandbox_policy.get(), + &config.legacy_sandbox_policy(), &SandboxPolicy::DangerFullAccess, "the legacy projection is intentionally lossy for managed unrestricted profiles" ); @@ -974,7 +974,7 @@ async fn permission_profile_override_applies_runtime_roots_to_legacy_projection( .can_write_path_with_cwd(memories_root.as_path(), cwd.path()) ); assert_eq!( - config.permissions.sandbox_policy.get(), + &config.legacy_sandbox_policy(), &SandboxPolicy::WorkspaceWrite { writable_roots: vec![memories_root], network_access: false, @@ -1209,7 +1209,7 @@ async fn permissions_profiles_allow_direct_write_roots_outside_workspace_root() .can_write_path_with_cwd(external_write_path.as_path(), cwd.path()) ); assert_eq!( - config.permissions.sandbox_policy.get(), + &config.legacy_sandbox_policy(), &SandboxPolicy::WorkspaceWrite { writable_roots: vec![external_write_path, memories_root], network_access: false, @@ -1317,7 +1317,7 @@ async fn permissions_profiles_allow_unknown_special_paths() -> std::io::Result<( }]), ); assert_eq!( - config.permissions.sandbox_policy.get(), + &config.legacy_sandbox_policy(), &SandboxPolicy::ReadOnly { network_access: false, } @@ -1382,7 +1382,7 @@ async fn permissions_profiles_allow_missing_filesystem_with_warning() -> std::io FileSystemSandboxPolicy::restricted(Vec::new()) ); assert_eq!( - config.permissions.sandbox_policy.get(), + &config.legacy_sandbox_policy(), &SandboxPolicy::ReadOnly { network_access: false, } @@ -1509,13 +1509,7 @@ async fn permissions_profiles_allow_network_enablement() -> std::io::Result<()> config.permissions.network_sandbox_policy().is_enabled(), "expected network sandbox policy to be enabled", ); - assert!( - config - .permissions - .sandbox_policy - .get() - .has_full_network_access() - ); + assert!(config.legacy_sandbox_policy().has_full_network_access()); Ok(()) } @@ -1799,7 +1793,7 @@ exclude_slash_tmp = true ) .await?; - let sandbox_policy = config.permissions.sandbox_policy.get(); + let sandbox_policy = &config.legacy_sandbox_policy(); assert_eq!( config.permissions.file_system_sandbox_policy(), FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(sandbox_policy, cwd.path()), @@ -1982,12 +1976,12 @@ async fn add_dir_override_extends_workspace_writable_roots() -> std::io::Result< let expected_backend = backend.abs(); if cfg!(target_os = "windows") { - match config.permissions.sandbox_policy.get() { + match &config.legacy_sandbox_policy() { SandboxPolicy::ReadOnly { .. } => {} other => panic!("expected read-only policy on Windows, got {other:?}"), } } else { - match config.permissions.sandbox_policy.get() { + match &config.legacy_sandbox_policy() { SandboxPolicy::WorkspaceWrite { writable_roots, .. } => { assert_eq!( writable_roots @@ -2045,7 +2039,7 @@ async fn workspace_write_always_includes_memories_root_once() -> std::io::Result .await?; if cfg!(target_os = "windows") { - match config.permissions.sandbox_policy.get() { + match &config.legacy_sandbox_policy() { SandboxPolicy::ReadOnly { .. } => {} other => panic!("expected read-only policy on Windows, got {other:?}"), } @@ -2056,7 +2050,7 @@ async fn workspace_write_always_includes_memories_root_once() -> std::io::Result memories_root.display() ); let expected_memories_root = memories_root.abs(); - match config.permissions.sandbox_policy.get() { + match &config.legacy_sandbox_policy() { SandboxPolicy::WorkspaceWrite { writable_roots, .. } => { assert_eq!( writable_roots @@ -2375,7 +2369,7 @@ async fn profile_sandbox_mode_overrides_base() -> std::io::Result<()> { .await?; assert!(matches!( - config.permissions.sandbox_policy.get(), + &config.legacy_sandbox_policy(), &SandboxPolicy::DangerFullAccess )); @@ -2409,12 +2403,12 @@ async fn cli_override_takes_precedence_over_profile_sandbox_mode() -> std::io::R if cfg!(target_os = "windows") { assert!(matches!( - config.permissions.sandbox_policy.get(), + &config.legacy_sandbox_policy(), SandboxPolicy::ReadOnly { .. } )); } else { assert!(matches!( - config.permissions.sandbox_policy.get(), + &config.legacy_sandbox_policy(), SandboxPolicy::WorkspaceWrite { .. } )); } @@ -5448,7 +5442,6 @@ async fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> { permissions: Permissions { approval_policy: Constrained::allow_any(AskForApproval::Never), permission_profile: Constrained::allow_any(PermissionProfile::read_only()), - sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()), network: None, allow_login_shell: true, shell_environment_policy: ShellEnvironmentPolicy::default(), @@ -5642,7 +5635,6 @@ async fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> { permissions: Permissions { approval_policy: Constrained::allow_any(AskForApproval::UnlessTrusted), permission_profile: Constrained::allow_any(PermissionProfile::read_only()), - sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()), network: None, allow_login_shell: true, shell_environment_policy: ShellEnvironmentPolicy::default(), @@ -5790,7 +5782,6 @@ async fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> { permissions: Permissions { approval_policy: Constrained::allow_any(AskForApproval::OnFailure), permission_profile: Constrained::allow_any(PermissionProfile::read_only()), - sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()), network: None, allow_login_shell: true, shell_environment_policy: ShellEnvironmentPolicy::default(), @@ -5923,7 +5914,6 @@ async fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> { permissions: Permissions { approval_policy: Constrained::allow_any(AskForApproval::OnFailure), permission_profile: Constrained::allow_any(PermissionProfile::read_only()), - sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()), network: None, allow_login_shell: true, shell_environment_policy: ShellEnvironmentPolicy::default(), @@ -6660,7 +6650,7 @@ async fn test_untrusted_project_gets_unless_trusted_approval_policy() -> anyhow: if cfg!(target_os = "windows") { assert!( matches!( - config.permissions.sandbox_policy.get(), + &config.legacy_sandbox_policy(), SandboxPolicy::ReadOnly { .. } ), "Expected ReadOnly on Windows" @@ -6668,7 +6658,7 @@ async fn test_untrusted_project_gets_unless_trusted_approval_policy() -> anyhow: } else { assert!( matches!( - config.permissions.sandbox_policy.get(), + &config.legacy_sandbox_policy(), SandboxPolicy::WorkspaceWrite { .. } ), "Expected WorkspaceWrite sandbox for untrusted project" @@ -6694,7 +6684,7 @@ async fn requirements_disallowing_default_sandbox_falls_back_to_required_default .build() .await?; assert_eq!( - *config.permissions.sandbox_policy.get(), + config.legacy_sandbox_policy(), SandboxPolicy::new_read_only_policy() ); Ok(()) @@ -6735,7 +6725,7 @@ async fn explicit_sandbox_mode_falls_back_when_disallowed_by_requirements() -> s .build() .await?; assert_eq!( - *config.permissions.sandbox_policy.get(), + config.legacy_sandbox_policy(), SandboxPolicy::new_read_only_policy() ); Ok(()) @@ -6764,10 +6754,7 @@ async fn permission_profile_override_falls_back_when_disallowed_by_requirements( .await?; let expected_sandbox_policy = SandboxPolicy::new_read_only_policy(); - assert_eq!( - *config.permissions.sandbox_policy.get(), - expected_sandbox_policy - ); + assert_eq!(config.legacy_sandbox_policy(), expected_sandbox_policy); assert_eq!( config.permissions.permission_profile(), PermissionProfile::read_only() @@ -6821,7 +6808,7 @@ async fn permission_profile_override_preserves_split_write_roots() -> std::io::R .can_write_path_with_cwd(outside_root.as_path(), config.cwd.as_path()) ); assert!(matches!( - config.permissions.sandbox_policy.get(), + &config.legacy_sandbox_policy(), SandboxPolicy::WorkspaceWrite { .. } )); assert_eq!( diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index eb6d10f8498e..9a7a9ca79d30 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -195,11 +195,6 @@ pub struct Permissions { /// Canonical effective runtime permissions after config requirements and /// runtime readable-root additions have been applied. pub permission_profile: Constrained, - /// Effective sandbox policy used for shell/unified exec. - /// - /// Legacy projection retained while runtime call sites migrate to - /// `permission_profile`. - pub sandbox_policy: Constrained, /// Effective network configuration applied to all spawned processes. pub network: Option, /// Whether the model may request a login shell for shell-based tools. @@ -250,13 +245,12 @@ impl Permissions { } /// Check whether a legacy sandbox policy can be applied to this permission - /// set under both legacy and canonical profile constraints. + /// set after projecting it into the canonical permission profile. pub fn can_set_legacy_sandbox_policy( &self, sandbox_policy: &SandboxPolicy, cwd: &Path, ) -> ConstraintResult<()> { - self.sandbox_policy.can_set(sandbox_policy)?; let file_system_sandbox_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(sandbox_policy, cwd); let network_sandbox_policy = NetworkSandboxPolicy::from(sandbox_policy); @@ -285,31 +279,18 @@ impl Permissions { network_sandbox_policy, ); - self.sandbox_policy.set(sandbox_policy)?; self.permission_profile.set(permission_profile)?; Ok(()) } - /// Replace permissions from the canonical profile and update compatibility - /// projections for legacy consumers. + /// Replace permissions from the canonical profile. pub fn set_permission_profile( &mut self, permission_profile: PermissionProfile, - cwd: &Path, ) -> ConstraintResult<()> { - let (file_system_sandbox_policy, network_sandbox_policy) = - permission_profile.to_runtime_permissions(); - let sandbox_policy = compatibility_sandbox_policy_for_permission_profile( - &permission_profile, - &file_system_sandbox_policy, - network_sandbox_policy, - cwd, - ); self.permission_profile.can_set(&permission_profile)?; - self.sandbox_policy.can_set(&sandbox_policy)?; self.permission_profile.set(permission_profile)?; - self.sandbox_policy.set(sandbox_policy)?; Ok(()) } } @@ -915,6 +896,18 @@ impl ConfigBuilder { } impl Config { + pub fn legacy_sandbox_policy(&self) -> SandboxPolicy { + self.permissions.legacy_sandbox_policy(self.cwd.as_path()) + } + + pub fn set_legacy_sandbox_policy( + &mut self, + sandbox_policy: SandboxPolicy, + ) -> ConstraintResult<()> { + self.permissions + .set_legacy_sandbox_policy(sandbox_policy, self.cwd.as_path()) + } + pub fn to_models_manager_config(&self) -> ModelsManagerConfig { ModelsManagerConfig { model_context_window: self.model_context_window, @@ -2484,7 +2477,6 @@ impl Config { permissions: Permissions { approval_policy: constrained_approval_policy.value, permission_profile: constrained_permission_profile, - sandbox_policy: constrained_sandbox_policy.value, network, allow_login_shell, shell_environment_policy, diff --git a/codex-rs/core/src/guardian/review_session.rs b/codex-rs/core/src/guardian/review_session.rs index fac589c58b8d..22651c23d8e6 100644 --- a/codex-rs/core/src/guardian/review_session.rs +++ b/codex-rs/core/src/guardian/review_session.rs @@ -848,7 +848,6 @@ pub(crate) fn build_guardian_review_session_config( guardian_config.permissions.permission_profile = Constrained::allow_only( PermissionProfile::from_legacy_sandbox_policy(&sandbox_policy), ); - guardian_config.permissions.sandbox_policy = Constrained::allow_only(sandbox_policy.clone()); guardian_config .permissions .set_legacy_sandbox_policy(sandbox_policy, guardian_config.cwd.as_path()) diff --git a/codex-rs/core/src/guardian/tests.rs b/codex-rs/core/src/guardian/tests.rs index 7b0f7904b071..19bee54e4c1b 100644 --- a/codex-rs/core/src/guardian/tests.rs +++ b/codex-rs/core/src/guardian/tests.rs @@ -1950,8 +1950,10 @@ async fn guardian_review_session_config_preserves_parent_network_proxy() { Constrained::allow_only(AskForApproval::Never) ); assert_eq!( - guardian_config.permissions.sandbox_policy, - Constrained::allow_only(SandboxPolicy::new_read_only_policy()) + guardian_config.permissions.permission_profile, + Constrained::allow_only(PermissionProfile::from_legacy_sandbox_policy( + &SandboxPolicy::new_read_only_policy(), + )) ); } diff --git a/codex-rs/core/src/memories/tests.rs b/codex-rs/core/src/memories/tests.rs index f718c309a297..08ebcd802a69 100644 --- a/codex-rs/core/src/memories/tests.rs +++ b/codex-rs/core/src/memories/tests.rs @@ -489,7 +489,7 @@ mod phase2 { ); config .permissions - .set_permission_profile(permission_profile, config.cwd.as_path()) + .set_permission_profile(permission_profile) .expect("permissions are configurable"); configure(&mut config); let config = Arc::new(config); @@ -935,8 +935,9 @@ mod phase2 { .await .expect("enqueue global consolidation"); let mut constrained_config = harness.config.as_ref().clone(); - constrained_config.permissions.sandbox_policy = - Constrained::allow_only(SandboxPolicy::DangerFullAccess); + constrained_config.permissions.permission_profile = Constrained::allow_only( + PermissionProfile::from_legacy_sandbox_policy(&SandboxPolicy::DangerFullAccess), + ); phase2::run(&harness.session, Arc::new(constrained_config)).await; diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index 290b90036f2e..951575532390 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -1515,7 +1515,9 @@ async fn session_configured_reports_permission_profile_for_external_sandbox() -> }; let expected_sandbox_policy = sandbox_policy.clone(); let mut builder = test_codex().with_config(move |config| { - config.permissions.sandbox_policy = codex_config::Constrained::allow_any(sandbox_policy); + config + .set_legacy_sandbox_policy(sandbox_policy) + .expect("set sandbox policy"); config.permissions.permission_profile = codex_config::Constrained::allow_any(PermissionProfile::from_runtime_permissions( &FileSystemSandboxPolicy::external_sandbox(), @@ -4187,7 +4189,7 @@ async fn user_turn_updates_approvals_reviewer() { cwd: config.cwd.to_path_buf(), approval_policy: config.permissions.approval_policy.value(), approvals_reviewer: Some(codex_config::types::ApprovalsReviewer::AutoReview), - sandbox_policy: config.permissions.sandbox_policy.get().clone(), + sandbox_policy: config.legacy_sandbox_policy(), permission_profile: None, model: turn_context.model_info.slug.clone(), effort: config.model_reasoning_effort, diff --git a/codex-rs/core/src/session/turn_context.rs b/codex-rs/core/src/session/turn_context.rs index 45b41e601e0d..383d80292c43 100644 --- a/codex-rs/core/src/session/turn_context.rs +++ b/codex-rs/core/src/session/turn_context.rs @@ -383,8 +383,6 @@ impl Session { per_turn_config.approvals_reviewer = session_configuration.approvals_reviewer; per_turn_config.permissions.permission_profile = session_configuration.permission_profile.clone(); - let sandbox_policy = session_configuration.sandbox_policy(); - per_turn_config.permissions.sandbox_policy = Constrained::allow_only(sandbox_policy); let permission_profile = session_configuration.permission_profile(); let resolved_web_search_mode = resolve_web_search_mode_for_turn(&per_turn_config.web_search_mode, &permission_profile); diff --git a/codex-rs/core/src/tools/handlers/multi_agents_common.rs b/codex-rs/core/src/tools/handlers/multi_agents_common.rs index 266666022993..c722ddb8d3e7 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_common.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_common.rs @@ -269,7 +269,7 @@ pub(crate) fn apply_spawn_agent_runtime_overrides( config.cwd = turn.cwd.clone(); config .permissions - .set_permission_profile(turn.permission_profile(), turn.cwd.as_path()) + .set_permission_profile(turn.permission_profile()) .map_err(|err| { FunctionCallError::RespondToModel(format!("permission_profile is invalid: {err}")) })?; diff --git a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs index e9c9406ae8bc..64cc9db032fb 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs @@ -2086,7 +2086,7 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() { let (mut session, mut turn) = make_session_and_context().await; let manager = thread_manager(); session.services.agent_control = manager.agent_control(); - let expected_sandbox = turn.config.permissions.sandbox_policy.get().clone(); + let expected_sandbox = turn.config.legacy_sandbox_policy(); let mut expected_file_system_sandbox_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&expected_sandbox, &turn.cwd); expected_file_system_sandbox_policy @@ -3585,8 +3585,9 @@ async fn tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtr #[tokio::test] async fn build_agent_spawn_config_uses_turn_context_values() { fn pick_allowed_sandbox_policy( - constraint: &crate::config::Constrained, + constraint: &crate::config::Constrained, base: SandboxPolicy, + cwd: &std::path::Path, ) -> SandboxPolicy { let candidates = [ SandboxPolicy::new_read_only_policy(), @@ -3595,7 +3596,21 @@ async fn build_agent_spawn_config_uses_turn_context_values() { ]; candidates .into_iter() - .find(|candidate| *candidate != base && constraint.can_set(candidate).is_ok()) + .find(|candidate| { + if *candidate == base { + return false; + } + let file_system_sandbox_policy = + FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(candidate, cwd); + let network_sandbox_policy = NetworkSandboxPolicy::from(candidate); + let permission_profile = + PermissionProfile::from_runtime_permissions_with_enforcement( + SandboxEnforcement::from_legacy_sandbox_policy(candidate), + &file_system_sandbox_policy, + network_sandbox_policy, + ); + constraint.can_set(&permission_profile).is_ok() + }) .unwrap_or(base) } @@ -3613,8 +3628,9 @@ async fn build_agent_spawn_config_uses_turn_context_values() { turn.cwd = temp_dir.abs(); turn.codex_linux_sandbox_exe = Some(PathBuf::from("/bin/echo")); let sandbox_policy = pick_allowed_sandbox_policy( - &turn.config.permissions.sandbox_policy, - turn.config.permissions.sandbox_policy.get().clone(), + &turn.config.permissions.permission_profile, + turn.config.legacy_sandbox_policy(), + turn.cwd.as_path(), ); let file_system_sandbox_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&sandbox_policy, &turn.cwd); @@ -3648,7 +3664,7 @@ async fn build_agent_spawn_config_uses_turn_context_values() { .expect("approval policy set"); expected .permissions - .set_permission_profile(permission_profile, turn.cwd.as_path()) + .set_permission_profile(permission_profile) .expect("permission profile set"); assert_eq!(config, expected); } @@ -3699,8 +3715,7 @@ async fn build_agent_resume_config_clears_base_instructions() { .expect("approval policy set"); expected .permissions - .sandbox_policy - .set(turn.sandbox_policy()) - .expect("sandbox policy set"); + .set_permission_profile(turn.permission_profile()) + .expect("permission profile set"); assert_eq!(config, expected); } diff --git a/codex-rs/core/tests/common/zsh_fork.rs b/codex-rs/core/tests/common/zsh_fork.rs index bc87c9ea93fa..448693e06948 100644 --- a/codex-rs/core/tests/common/zsh_fork.rs +++ b/codex-rs/core/tests/common/zsh_fork.rs @@ -36,7 +36,9 @@ impl ZshForkRuntime { config.main_execve_wrapper_exe = Some(self.main_execve_wrapper_exe.clone()); config.permissions.allow_login_shell = false; config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy); + config + .set_legacy_sandbox_policy(sandbox_policy) + .expect("set sandbox policy"); } } diff --git a/codex-rs/core/tests/suite/agent_websocket.rs b/codex-rs/core/tests/suite/agent_websocket.rs index fb0cd84120e6..305346afac99 100644 --- a/codex-rs/core/tests/suite/agent_websocket.rs +++ b/codex-rs/core/tests/suite/agent_websocket.rs @@ -38,11 +38,8 @@ async fn websocket_test_codex_shell_chain() -> Result<()> { let mut builder = test_codex().with_windows_cmd_shell(); let test = builder.build_with_websocket_server(&server).await?; - test.submit_turn_with_policy( - "run the echo command", - test.config.permissions.sandbox_policy.get().clone(), - ) - .await?; + test.submit_turn_with_policy("run the echo command", test.config.legacy_sandbox_policy()) + .await?; let connection = server.single_connection(); assert_eq!(connection.len(), 2); @@ -85,11 +82,8 @@ async fn websocket_first_turn_uses_startup_prewarm_and_create() -> Result<()> { let mut builder = test_codex(); let test = builder.build_with_websocket_server(&server).await?; - test.submit_turn_with_policy( - "hello", - test.config.permissions.sandbox_policy.get().clone(), - ) - .await?; + test.submit_turn_with_policy("hello", test.config.legacy_sandbox_policy()) + .await?; assert_eq!(server.handshakes().len(), 1); let connection = server.single_connection(); @@ -135,11 +129,8 @@ async fn websocket_first_turn_handles_handshake_delay_with_startup_prewarm() -> let mut builder = test_codex(); let test = builder.build_with_websocket_server(&server).await?; - test.submit_turn_with_policy( - "hello", - test.config.permissions.sandbox_policy.get().clone(), - ) - .await?; + test.submit_turn_with_policy("hello", test.config.legacy_sandbox_policy()) + .await?; assert_eq!(server.handshakes().len(), 1); let connection = server.single_connection(); @@ -191,11 +182,8 @@ async fn websocket_v2_test_codex_shell_chain() -> Result<()> { }); let test = builder.build_with_websocket_server(&server).await?; - test.submit_turn_with_policy( - "run the echo command", - test.config.permissions.sandbox_policy.get().clone(), - ) - .await?; + test.submit_turn_with_policy("run the echo command", test.config.legacy_sandbox_policy()) + .await?; let connection = server.single_connection(); assert_eq!(connection.len(), 3); diff --git a/codex-rs/core/tests/suite/approvals.rs b/codex-rs/core/tests/suite/approvals.rs index 0888c91c4743..4209fc2100d5 100644 --- a/codex-rs/core/tests/suite/approvals.rs +++ b/codex-rs/core/tests/suite/approvals.rs @@ -1727,7 +1727,9 @@ async fn run_scenario(scenario: &ScenarioSpec) -> Result<()> { let mut builder = test_codex().with_model(model).with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy.clone()); + config + .set_legacy_sandbox_policy(sandbox_policy.clone()) + .expect("set sandbox policy"); for feature in features { config .features @@ -1854,7 +1856,9 @@ async fn approving_apply_patch_for_session_skips_future_prompts_for_same_file() .with_model("gpt-5.4") .with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); config.approvals_reviewer = ApprovalsReviewer::User; }); let test = builder.build(&server).await?; @@ -1962,7 +1966,9 @@ async fn approving_execpolicy_amendment_persists_policy_and_skips_future_prompts let sandbox_policy_for_config = sandbox_policy.clone(); let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); }); let test = builder.build(&server).await?; let allow_prefix_path = test.cwd.path().join("allow-prefix.txt"); @@ -2133,7 +2139,9 @@ async fn spawned_subagent_execpolicy_amendment_propagates_to_parent_session() -> let sandbox_policy_for_config = sandbox_policy.clone(); let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); config .features .enable(Feature::Collab) @@ -2394,7 +2402,9 @@ async fn invalid_requested_prefix_rule_falls_back_for_compound_command() -> Resu let sandbox_policy_for_config = sandbox_policy.clone(); let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); }); let test = builder.build(&server).await?; @@ -2445,7 +2455,9 @@ async fn approving_fallback_rule_for_compound_command_works() -> Result<()> { let sandbox_policy_for_config = sandbox_policy.clone(); let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); }); let test = builder.build(&server).await?; @@ -2580,7 +2592,9 @@ allow_local_binding = true let sandbox_policy_for_config = sandbox_policy.clone(); let mut builder = test_codex().with_home(home).with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); let layers = config .config_layer_stack .get_layers( @@ -3030,7 +3044,9 @@ async fn compound_command_with_one_safe_command_still_requires_approval() -> Res let sandbox_policy_for_config = sandbox_policy.clone(); let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); }); let test = builder.build(&server).await?; diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index 13cdf3867450..888656843534 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -1745,7 +1745,7 @@ async fn user_turn_collaboration_mode_overrides_model_and_effort() -> anyhow::Re cwd: config.cwd.to_path_buf(), approval_policy: config.permissions.approval_policy.value(), approvals_reviewer: None, - sandbox_policy: config.permissions.sandbox_policy.get().clone(), + sandbox_policy: config.legacy_sandbox_policy(), permission_profile: None, model: session_configured.model.clone(), effort: Some(ReasoningEffort::Low), @@ -1867,7 +1867,7 @@ async fn user_turn_explicit_reasoning_summary_overrides_model_catalog_default() cwd: config.cwd.to_path_buf(), approval_policy: config.permissions.approval_policy.value(), approvals_reviewer: None, - sandbox_policy: config.permissions.sandbox_policy.get().clone(), + sandbox_policy: config.legacy_sandbox_policy(), permission_profile: None, model: session_configured.model, effort: None, diff --git a/codex-rs/core/tests/suite/codex_delegate.rs b/codex-rs/core/tests/suite/codex_delegate.rs index 0b96c6e5abcc..461b07284ce1 100644 --- a/codex-rs/core/tests/suite/codex_delegate.rs +++ b/codex-rs/core/tests/suite/codex_delegate.rs @@ -64,8 +64,9 @@ async fn codex_delegate_forwards_exec_approval_and_proceeds_on_approval() { // routes ExecApprovalRequest via the parent. let mut builder = test_codex().with_model("gpt-5.4").with_config(|config| { config.permissions.approval_policy = Constrained::allow_any(AskForApproval::OnRequest); - config.permissions.sandbox_policy = - Constrained::allow_any(SandboxPolicy::new_read_only_policy()); + config + .set_legacy_sandbox_policy(SandboxPolicy::new_read_only_policy()) + .expect("set sandbox policy"); }); let test = builder.build(&server).await.expect("build test codex"); @@ -147,8 +148,9 @@ async fn codex_delegate_forwards_patch_approval_and_proceeds_on_decision() { let mut builder = test_codex().with_model("gpt-5.4").with_config(|config| { config.permissions.approval_policy = Constrained::allow_any(AskForApproval::OnRequest); // Use a restricted sandbox so patch approval is required - config.permissions.sandbox_policy = - Constrained::allow_any(SandboxPolicy::new_read_only_policy()); + config + .set_legacy_sandbox_policy(SandboxPolicy::new_read_only_policy()) + .expect("set sandbox policy"); config.include_apply_patch_tool = true; }); let test = builder.build(&server).await.expect("build test codex"); diff --git a/codex-rs/core/tests/suite/collaboration_instructions.rs b/codex-rs/core/tests/suite/collaboration_instructions.rs index 26d8d6aacc16..e3ea0669ca55 100644 --- a/codex-rs/core/tests/suite/collaboration_instructions.rs +++ b/codex-rs/core/tests/suite/collaboration_instructions.rs @@ -185,7 +185,7 @@ async fn collaboration_instructions_added_on_user_turn() -> Result<()> { cwd: test.config.cwd.to_path_buf(), approval_policy: test.config.permissions.approval_policy.value(), approvals_reviewer: None, - sandbox_policy: test.config.permissions.sandbox_policy.get().clone(), + sandbox_policy: test.config.legacy_sandbox_policy(), permission_profile: None, model: test.session_configured.model.clone(), effort: None, @@ -307,7 +307,7 @@ async fn user_turn_overrides_collaboration_instructions_after_override() -> Resu cwd: test.config.cwd.to_path_buf(), approval_policy: test.config.permissions.approval_policy.value(), approvals_reviewer: None, - sandbox_policy: test.config.permissions.sandbox_policy.get().clone(), + sandbox_policy: test.config.legacy_sandbox_policy(), permission_profile: None, model: test.session_configured.model.clone(), effort: None, diff --git a/codex-rs/core/tests/suite/hooks.rs b/codex-rs/core/tests/suite/hooks.rs index 851980c42fa9..74e9a7a6824d 100644 --- a/codex-rs/core/tests/suite/hooks.rs +++ b/codex-rs/core/tests/suite/hooks.rs @@ -1583,7 +1583,9 @@ allow_local_binding = true .enable(Feature::CodexHooks) .expect("test config should allow feature update"); config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); let layers = config .config_layer_stack .get_layers( diff --git a/codex-rs/core/tests/suite/otel.rs b/codex-rs/core/tests/suite/otel.rs index 6407ec2702f5..3d2f5102e230 100644 --- a/codex-rs/core/tests/suite/otel.rs +++ b/codex-rs/core/tests/suite/otel.rs @@ -1110,8 +1110,9 @@ async fn handle_container_exec_autoapprove_from_config_records_tool_decision() { let TestCodex { codex, .. } = test_codex() .with_config(|config| { config.permissions.approval_policy = Constrained::allow_any(AskForApproval::OnRequest); - config.permissions.sandbox_policy = - Constrained::allow_any(SandboxPolicy::DangerFullAccess); + config + .set_legacy_sandbox_policy(SandboxPolicy::DangerFullAccess) + .expect("set sandbox policy"); }) .build(&server) .await diff --git a/codex-rs/core/tests/suite/prompt_caching.rs b/codex-rs/core/tests/suite/prompt_caching.rs index 2e168bd7297c..3852918c53db 100644 --- a/codex-rs/core/tests/suite/prompt_caching.rs +++ b/codex-rs/core/tests/suite/prompt_caching.rs @@ -825,7 +825,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a let default_cwd = config.cwd.clone(); let default_approval_policy = config.permissions.approval_policy.value(); - let default_sandbox_policy = config.permissions.sandbox_policy.get(); + let default_sandbox_policy = &config.legacy_sandbox_policy(); let default_model = session_configured.model; let default_effort = config.model_reasoning_effort; let default_summary = config.model_reasoning_summary; @@ -955,7 +955,7 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu let default_cwd = config.cwd.clone(); let default_approval_policy = config.permissions.approval_policy.value(); - let default_sandbox_policy = config.permissions.sandbox_policy.get(); + let default_sandbox_policy = &config.legacy_sandbox_policy(); let default_model = session_configured.model; let default_effort = config.model_reasoning_effort; let default_summary = config.model_reasoning_summary; diff --git a/codex-rs/core/tests/suite/remote_models.rs b/codex-rs/core/tests/suite/remote_models.rs index 07a1bc404d1d..a69dae4a5b22 100644 --- a/codex-rs/core/tests/suite/remote_models.rs +++ b/codex-rs/core/tests/suite/remote_models.rs @@ -162,7 +162,7 @@ async fn remote_models_config_context_window_override_clamps_to_max_context_wind cwd: cwd.path().to_path_buf(), approval_policy: config.permissions.approval_policy.value(), approvals_reviewer: None, - sandbox_policy: config.permissions.sandbox_policy.get().clone(), + sandbox_policy: config.legacy_sandbox_policy(), model: requested_model.to_string(), effort: None, summary: None, @@ -240,7 +240,7 @@ async fn remote_models_config_override_above_max_uses_max_context_window() -> Re cwd: cwd.path().to_path_buf(), approval_policy: config.permissions.approval_policy.value(), approvals_reviewer: None, - sandbox_policy: config.permissions.sandbox_policy.get().clone(), + sandbox_policy: config.legacy_sandbox_policy(), model: requested_model.to_string(), effort: None, summary: None, @@ -317,7 +317,7 @@ async fn remote_models_use_context_window_when_config_override_is_absent() -> Re cwd: cwd.path().to_path_buf(), approval_policy: config.permissions.approval_policy.value(), approvals_reviewer: None, - sandbox_policy: config.permissions.sandbox_policy.get().clone(), + sandbox_policy: config.legacy_sandbox_policy(), model: requested_model.to_string(), effort: None, summary: None, @@ -407,7 +407,7 @@ async fn remote_models_long_model_slug_is_sent_with_high_reasoning() -> Result<( cwd: cwd.path().to_path_buf(), approval_policy: config.permissions.approval_policy.value(), approvals_reviewer: None, - sandbox_policy: config.permissions.sandbox_policy.get().clone(), + sandbox_policy: config.legacy_sandbox_policy(), permission_profile: None, model: requested_model.to_string(), effort: None, @@ -468,7 +468,7 @@ async fn namespaced_model_slug_uses_catalog_metadata_without_fallback_warning() cwd: cwd.path().to_path_buf(), approval_policy: config.permissions.approval_policy.value(), approvals_reviewer: None, - sandbox_policy: config.permissions.sandbox_policy.get().clone(), + sandbox_policy: config.legacy_sandbox_policy(), permission_profile: None, model: requested_model.to_string(), effort: None, diff --git a/codex-rs/core/tests/suite/request_permissions.rs b/codex-rs/core/tests/suite/request_permissions.rs index 8719bba9ff82..455c1fabb9d5 100644 --- a/codex-rs/core/tests/suite/request_permissions.rs +++ b/codex-rs/core/tests/suite/request_permissions.rs @@ -324,7 +324,9 @@ async fn with_additional_permissions_requires_approval_under_on_request() -> Res let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); config .features .enable(Feature::ExecPermissionApprovals) @@ -419,7 +421,9 @@ async fn request_permissions_tool_is_auto_denied_when_granular_request_permissio let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); config .features .enable(Feature::RequestPermissionsTool) @@ -502,7 +506,9 @@ async fn relative_additional_permissions_resolve_against_tool_workdir() -> Resul let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); config .features .enable(Feature::ExecPermissionApprovals) @@ -603,7 +609,9 @@ async fn read_only_with_additional_permissions_does_not_widen_to_unrequested_cwd let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); config .features .enable(Feature::ExecPermissionApprovals) @@ -703,7 +711,9 @@ async fn read_only_with_additional_permissions_does_not_widen_to_unrequested_tmp let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); config .features .enable(Feature::ExecPermissionApprovals) @@ -802,7 +812,9 @@ async fn workspace_write_with_additional_permissions_can_write_outside_cwd() -> let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); config .features .enable(Feature::ExecPermissionApprovals) @@ -906,7 +918,9 @@ async fn with_additional_permissions_denied_approval_blocks_execution() -> Resul let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); config .features .enable(Feature::ExecPermissionApprovals) @@ -1011,7 +1025,9 @@ async fn request_permissions_grants_apply_to_later_exec_command_calls() -> Resul let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); config .features .enable(Feature::ExecPermissionApprovals) @@ -1135,7 +1151,9 @@ async fn request_permissions_preapprove_explicit_exec_permissions_outside_on_req let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); config .features .enable(Feature::ExecPermissionApprovals) @@ -1253,7 +1271,9 @@ async fn request_permissions_grants_apply_to_later_shell_command_calls() -> Resu let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); config .features .enable(Feature::ExecPermissionApprovals) @@ -1365,7 +1385,9 @@ async fn request_permissions_grants_apply_to_later_shell_command_calls_without_i let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); config .features .enable(Feature::RequestPermissionsTool) @@ -1477,7 +1499,9 @@ async fn partial_request_permissions_grants_do_not_preapprove_new_permissions() let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); config .features .enable(Feature::ExecPermissionApprovals) @@ -1641,7 +1665,9 @@ async fn request_permissions_grants_do_not_carry_across_turns() -> Result<()> { let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); config .features .enable(Feature::ExecPermissionApprovals) @@ -1754,7 +1780,9 @@ async fn request_permissions_session_grants_carry_across_turns() -> Result<()> { let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); config .features .enable(Feature::ExecPermissionApprovals) diff --git a/codex-rs/core/tests/suite/request_permissions_tool.rs b/codex-rs/core/tests/suite/request_permissions_tool.rs index 8bd83f58b583..8baf14293c0b 100644 --- a/codex-rs/core/tests/suite/request_permissions_tool.rs +++ b/codex-rs/core/tests/suite/request_permissions_tool.rs @@ -204,7 +204,9 @@ async fn approved_folder_write_request_permissions_unblocks_later_exec_without_s let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); config .features .enable(Feature::ExecPermissionApprovals) @@ -334,7 +336,9 @@ async fn apply_patch_after_request_permissions(strict_auto_review: bool) -> Resu let mut builder = test_codex().with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config); + config + .set_legacy_sandbox_policy(sandbox_policy_for_config) + .expect("set sandbox policy"); config .features .enable(Feature::ExecPermissionApprovals) diff --git a/codex-rs/core/tests/suite/resume_warning.rs b/codex-rs/core/tests/suite/resume_warning.rs index ee3c7bbf3348..f5810956ab6c 100644 --- a/codex-rs/core/tests/suite/resume_warning.rs +++ b/codex-rs/core/tests/suite/resume_warning.rs @@ -32,7 +32,7 @@ fn resume_history( current_date: None, timezone: None, approval_policy: config.permissions.approval_policy.value(), - sandbox_policy: config.permissions.sandbox_policy.get().clone(), + sandbox_policy: config.legacy_sandbox_policy(), permission_profile: None, network: None, file_system_sandbox_policy: None, diff --git a/codex-rs/core/tests/suite/tools.rs b/codex-rs/core/tests/suite/tools.rs index 46bedff36e62..aff8755b1108 100644 --- a/codex-rs/core/tests/suite/tools.rs +++ b/codex-rs/core/tests/suite/tools.rs @@ -552,7 +552,9 @@ async fn shell_enforces_glob_deny_read_policy() -> Result<()> { let mut builder = test_codex() .with_model("gpt-5.4") .with_config(move |config| { - config.permissions.sandbox_policy = Constrained::allow_any(read_only_policy_for_config); + 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 @@ -789,9 +791,7 @@ async fn shell_timeout_handles_background_grandchild_stdout() -> Result<()> { let server = start_mock_server().await; let mut builder = test_codex().with_model("gpt-5.4").with_config(|config| { config - .permissions - .sandbox_policy - .set(SandboxPolicy::DangerFullAccess) + .set_legacy_sandbox_policy(SandboxPolicy::DangerFullAccess) .expect("set sandbox policy"); }); let test = builder.build(&server).await?; @@ -885,9 +885,7 @@ async fn shell_spawn_failure_truncates_exec_error() -> Result<()> { let server = start_mock_server().await; let mut builder = test_codex().with_config(|cfg| { - cfg.permissions - .sandbox_policy - .set(SandboxPolicy::DangerFullAccess) + cfg.set_legacy_sandbox_policy(SandboxPolicy::DangerFullAccess) .expect("set sandbox policy"); }); let test = builder.build(&server).await?; diff --git a/codex-rs/core/tests/suite/unified_exec.rs b/codex-rs/core/tests/suite/unified_exec.rs index 67226ef20e35..9c3272882e2f 100644 --- a/codex-rs/core/tests/suite/unified_exec.rs +++ b/codex-rs/core/tests/suite/unified_exec.rs @@ -2545,7 +2545,9 @@ async fn unified_exec_enforces_glob_deny_read_policy() -> Result<()> { .features .enable(Feature::UnifiedExec) .expect("test config should allow feature update"); - config.permissions.sandbox_policy = Constrained::allow_any(read_only_policy_for_config); + 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 diff --git a/codex-rs/tui/src/app/tests.rs b/codex-rs/tui/src/app/tests.rs index da83a72c400e..e81f4476f40e 100644 --- a/codex-rs/tui/src/app/tests.rs +++ b/codex-rs/tui/src/app/tests.rs @@ -1634,11 +1634,7 @@ async fn update_feature_flags_enabling_guardian_selects_auto_review() -> Result< auto_review.approval_policy ); assert_eq!( - app.chat_widget - .config_ref() - .permissions - .sandbox_policy - .get(), + &app.chat_widget.config_ref().legacy_sandbox_policy(), &auto_review.sandbox_policy ); assert_eq!( @@ -1714,9 +1710,7 @@ async fn update_feature_flags_disabling_guardian_clears_review_policy_and_restor .approval_policy .set(AskForApproval::OnRequest)?; app.config - .permissions - .sandbox_policy - .set(SandboxPolicy::new_workspace_write_policy())?; + .set_legacy_sandbox_policy(SandboxPolicy::new_workspace_write_policy())?; app.chat_widget .set_approval_policy(AskForApproval::OnRequest); app.chat_widget @@ -1815,11 +1809,7 @@ async fn update_feature_flags_enabling_guardian_overrides_explicit_manual_review auto_review.approval_policy ); assert_eq!( - app.chat_widget - .config_ref() - .permissions - .sandbox_policy - .get(), + &app.chat_widget.config_ref().legacy_sandbox_policy(), &auto_review.sandbox_policy ); assert_eq!( @@ -2933,7 +2923,7 @@ async fn side_fork_config_is_ephemeral_and_appends_developer_guardrails() { let mut app = make_test_app().await; app.config.developer_instructions = Some("Existing developer policy.".to_string()); let original_approval_policy = app.config.permissions.approval_policy.value(); - let original_sandbox_policy = app.config.permissions.sandbox_policy.get().clone(); + let original_sandbox_policy = app.config.legacy_sandbox_policy(); let fork_config = app.side_fork_config(); @@ -2942,10 +2932,7 @@ async fn side_fork_config_is_ephemeral_and_appends_developer_guardrails() { fork_config.permissions.approval_policy.value(), original_approval_policy ); - assert_eq!( - fork_config.permissions.sandbox_policy.get(), - &original_sandbox_policy - ); + assert_eq!(fork_config.legacy_sandbox_policy(), original_sandbox_policy); let developer_instructions = fork_config .developer_instructions .as_deref() diff --git a/codex-rs/tui/src/app/thread_session_state.rs b/codex-rs/tui/src/app/thread_session_state.rs index 2b242890d37f..b4d0fb26849b 100644 --- a/codex-rs/tui/src/app/thread_session_state.rs +++ b/codex-rs/tui/src/app/thread_session_state.rs @@ -192,9 +192,8 @@ mod tests { .set_sandbox_policy(expected_sandbox_policy.clone()) .expect("set widget sandbox policy"); app.config - .permissions - .set_legacy_sandbox_policy(expected_sandbox_policy.clone(), app.config.cwd.as_path()) - .expect("set app sandbox policy"); + .set_legacy_sandbox_policy(expected_sandbox_policy.clone()) + .expect("set sandbox policy"); app.sync_active_thread_permission_settings_to_cached_session() .await; diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index dd48f030cbd8..a1035fac724a 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -141,8 +141,12 @@ use codex_protocol::items::AgentMessageContent; use codex_protocol::items::AgentMessageItem; use codex_protocol::items::UserMessageItem; use codex_protocol::models::MessagePhase; +use codex_protocol::models::PermissionProfile; +use codex_protocol::models::SandboxEnforcement; use codex_protocol::models::local_image_label_text; use codex_protocol::parse_command::ParsedCommand; +use codex_protocol::permissions::FileSystemSandboxPolicy; +use codex_protocol::permissions::NetworkSandboxPolicy; use codex_protocol::plan_tool::PlanItemArg as UpdatePlanItemArg; use codex_protocol::plan_tool::StepStatus as UpdatePlanItemStatus; #[cfg(test)] @@ -2369,7 +2373,7 @@ impl ChatWidget { Some(permission_profile) => self .config .permissions - .set_permission_profile(permission_profile, event.cwd.as_path()), + .set_permission_profile(permission_profile), None => self .config .permissions @@ -2377,11 +2381,16 @@ impl ChatWidget { }; if let Err(err) = permission_sync { tracing::warn!(%err, "failed to sync permissions from SessionConfigured"); - self.config.permissions.sandbox_policy = - Constrained::allow_only(event.sandbox_policy.clone()); let permission_profile = event.permission_profile.clone().unwrap_or_else(|| { - codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy( - &event.sandbox_policy, + let file_system_sandbox_policy = + FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd( + &event.sandbox_policy, + event.cwd.as_path(), + ); + PermissionProfile::from_runtime_permissions_with_enforcement( + SandboxEnforcement::from_legacy_sandbox_policy(&event.sandbox_policy), + &file_system_sandbox_policy, + NetworkSandboxPolicy::from(&event.sandbox_policy), ) }); self.config.permissions.permission_profile = diff --git a/codex-rs/tui/src/chatwidget/tests/history_replay.rs b/codex-rs/tui/src/chatwidget/tests/history_replay.rs index be0fd03a1119..77f52524f47e 100644 --- a/codex-rs/tui/src/chatwidget/tests/history_replay.rs +++ b/codex-rs/tui/src/chatwidget/tests/history_replay.rs @@ -252,9 +252,7 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() { .set(AskForApproval::OnRequest) .expect("set approval policy"); chat.config - .permissions - .sandbox_policy - .set(SandboxPolicy::new_workspace_write_policy()) + .set_legacy_sandbox_policy(SandboxPolicy::new_workspace_write_policy()) .expect("set sandbox policy"); chat.config.cwd = test_path_buf("/home/user/main").abs(); @@ -312,7 +310,7 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() { AskForApproval::Never ); assert_eq!( - chat.config_ref().permissions.sandbox_policy.get(), + &chat.config_ref().legacy_sandbox_policy(), &expected_sandbox ); assert_eq!( @@ -374,7 +372,7 @@ async fn session_configured_external_sandbox_keeps_external_runtime_policy() { }); assert_eq!( - chat.config_ref().permissions.sandbox_policy.get(), + &chat.config_ref().legacy_sandbox_policy(), &expected_sandbox ); assert_eq!( diff --git a/codex-rs/tui/src/chatwidget/tests/permissions.rs b/codex-rs/tui/src/chatwidget/tests/permissions.rs index ccab18bfcb66..388bc67f81c3 100644 --- a/codex-rs/tui/src/chatwidget/tests/permissions.rs +++ b/codex-rs/tui/src/chatwidget/tests/permissions.rs @@ -1,13 +1,6 @@ use super::*; use pretty_assertions::assert_eq; -fn set_legacy_sandbox_policy(chat: &mut ChatWidget, sandbox_policy: SandboxPolicy) { - chat.config - .permissions - .set_legacy_sandbox_policy(sandbox_policy, chat.config.cwd.as_path()) - .expect("set sandbox policy"); -} - #[tokio::test] async fn approvals_selection_popup_snapshot() { let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; @@ -354,7 +347,9 @@ async fn permissions_selection_history_snapshot_full_access_to_default() { .approval_policy .set(AskForApproval::Never) .expect("set approval policy"); - set_legacy_sandbox_policy(&mut chat, SandboxPolicy::DangerFullAccess); + chat.config + .set_legacy_sandbox_policy(SandboxPolicy::DangerFullAccess) + .expect("set sandbox policy"); chat.open_permissions_popup(); let popup = render_bottom_popup(&chat, /*width*/ 120); @@ -393,7 +388,9 @@ async fn permissions_selection_emits_history_cell_when_current_is_selected() { .approval_policy .set(AskForApproval::OnRequest) .expect("set approval policy"); - set_legacy_sandbox_policy(&mut chat, SandboxPolicy::new_workspace_write_policy()); + chat.config + .set_legacy_sandbox_policy(SandboxPolicy::new_workspace_write_policy()) + .expect("set sandbox policy"); chat.open_permissions_popup(); chat.handle_key_event(KeyEvent::from(KeyCode::Enter)); @@ -448,7 +445,9 @@ async fn permissions_selection_hides_auto_review_when_feature_disabled_even_if_a .approval_policy .set(AskForApproval::OnRequest) .expect("set approval policy"); - set_legacy_sandbox_policy(&mut chat, SandboxPolicy::new_workspace_write_policy()); + chat.config + .set_legacy_sandbox_policy(SandboxPolicy::new_workspace_write_policy()) + .expect("set sandbox policy"); chat.open_permissions_popup(); let popup = render_bottom_popup(&chat, /*width*/ 120); @@ -573,7 +572,9 @@ async fn permissions_selection_can_disable_auto_review() { .approval_policy .set(AskForApproval::OnRequest) .expect("set approval policy"); - set_legacy_sandbox_policy(&mut chat, SandboxPolicy::new_workspace_write_policy()); + chat.config + .set_legacy_sandbox_policy(SandboxPolicy::new_workspace_write_policy()) + .expect("set sandbox policy"); chat.open_permissions_popup(); chat.handle_key_event(KeyEvent::from(KeyCode::Up)); @@ -610,7 +611,9 @@ async fn permissions_selection_sends_approvals_reviewer_in_override_turn_context .approval_policy .set(AskForApproval::OnRequest) .expect("set approval policy"); - set_legacy_sandbox_policy(&mut chat, SandboxPolicy::new_workspace_write_policy()); + chat.config + .set_legacy_sandbox_policy(SandboxPolicy::new_workspace_write_policy()) + .expect("set sandbox policy"); chat.set_approvals_reviewer(ApprovalsReviewer::User); chat.open_permissions_popup(); diff --git a/codex-rs/tui/src/status/tests.rs b/codex-rs/tui/src/status/tests.rs index 569f093a1155..03a988c793ca 100644 --- a/codex-rs/tui/src/status/tests.rs +++ b/codex-rs/tui/src/status/tests.rs @@ -99,16 +99,12 @@ async fn status_snapshot_includes_reasoning_details() { config.model_reasoning_summary = Some(ReasoningSummary::Detailed); config.cwd = test_path_buf("/workspace/tests").abs(); config - .permissions - .set_legacy_sandbox_policy( - SandboxPolicy::WorkspaceWrite { - writable_roots: Vec::new(), - network_access: false, - exclude_tmpdir_env_var: false, - exclude_slash_tmp: false, - }, - config.cwd.as_path(), - ) + .set_legacy_sandbox_policy(SandboxPolicy::WorkspaceWrite { + writable_roots: Vec::new(), + network_access: false, + exclude_tmpdir_env_var: false, + exclude_slash_tmp: false, + }) .expect("set sandbox policy"); let account_display = test_status_account_display(); @@ -185,16 +181,12 @@ async fn status_permissions_non_default_workspace_write_is_custom() { .expect("set approval policy"); config.cwd = test_path_buf("/workspace/tests").abs(); config - .permissions - .set_legacy_sandbox_policy( - SandboxPolicy::WorkspaceWrite { - writable_roots: Vec::new(), - network_access: true, - exclude_tmpdir_env_var: false, - exclude_slash_tmp: false, - }, - config.cwd.as_path(), - ) + .set_legacy_sandbox_policy(SandboxPolicy::WorkspaceWrite { + writable_roots: Vec::new(), + network_access: true, + exclude_tmpdir_env_var: false, + exclude_slash_tmp: false, + }) .expect("set sandbox policy"); let account_display = test_status_account_display();