From b2136b6aa51de5ae3e18d5cb6ca105554e00824f Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Sun, 31 May 2026 13:50:45 -0700 Subject: [PATCH] core: remove SandboxPolicy from production core --- .../command_exec_processor.rs | 4 - .../request_processors/thread_processor.rs | 13 ++- .../src/request_processors/turn_processor.rs | 50 +++++++++- codex-rs/codex-mcp/src/runtime.rs | 26 +++++ codex-rs/core/README.md | 26 ++--- codex-rs/core/src/agent/role_tests.rs | 9 +- codex-rs/core/src/codex_thread.rs | 16 --- .../core/src/config/config_loader_tests.rs | 16 +++ codex-rs/core/src/config/config_tests.rs | 16 +++ codex-rs/core/src/config/mod.rs | 98 +------------------ codex-rs/core/src/exec.rs | 54 +++++----- codex-rs/core/src/exec_tests.rs | 69 ++++++++++--- codex-rs/core/src/guardian/review_session.rs | 4 - codex-rs/core/src/mcp_tool_call.rs | 18 ++-- codex-rs/core/src/sandboxing/mod.rs | 11 --- codex-rs/core/src/session/handlers.rs | 61 ++++++++---- codex-rs/core/src/session/mod.rs | 1 - .../session/rollout_reconstruction_tests.rs | 30 ++++-- codex-rs/core/src/session/session.rs | 61 +++++------- codex-rs/core/src/session/tests.rs | 49 +++++++--- codex-rs/core/src/session/turn.rs | 2 +- codex-rs/core/src/session/turn_context.rs | 34 ++++--- codex-rs/core/src/spawn.rs | 2 +- .../src/tools/handlers/multi_agents_tests.rs | 62 +++++++++++- codex-rs/core/tests/common/lib.rs | 56 +++++++++++ codex-rs/core/tests/suite/agent_websocket.rs | 19 ++-- codex-rs/core/tests/suite/approvals.rs | 36 +++---- codex-rs/core/tests/suite/client.rs | 5 +- .../tests/suite/collaboration_instructions.rs | 7 +- codex-rs/core/tests/suite/guardian_review.rs | 4 +- codex-rs/core/tests/suite/prompt_caching.rs | 5 +- codex-rs/core/tests/suite/resume_warning.rs | 3 +- codex-rs/memories/write/src/phase2.rs | 43 ++++++-- codex-rs/otel/src/events/session_telemetry.rs | 3 +- codex-rs/protocol/src/models.rs | 36 +++++++ codex-rs/protocol/src/protocol.rs | 31 ++++++ codex-rs/sandboxing/src/manager.rs | 37 +------ .../sandbox-summary/src/config_summary.rs | 10 +- 38 files changed, 633 insertions(+), 394 deletions(-) diff --git a/codex-rs/app-server/src/request_processors/command_exec_processor.rs b/codex-rs/app-server/src/request_processors/command_exec_processor.rs index 5b14f0b0c0..3b875cdeb2 100644 --- a/codex-rs/app-server/src/request_processors/command_exec_processor.rs +++ b/codex-rs/app-server/src/request_processors/command_exec_processor.rs @@ -234,10 +234,6 @@ impl CommandExecRequestProcessor { config.effective_workspace_roots(), ) } else if let Some(policy) = sandbox_policy.map(|policy| policy.to_core()) { - self.config - .permissions - .can_set_legacy_sandbox_policy(&policy, &sandbox_cwd) - .map_err(|err| invalid_request(format!("invalid sandbox policy: {err}")))?; let file_system_sandbox_policy = codex_protocol::permissions::FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&policy, &sandbox_cwd); let network_sandbox_policy = diff --git a/codex-rs/app-server/src/request_processors/thread_processor.rs b/codex-rs/app-server/src/request_processors/thread_processor.rs index 6440f0786d..cce6c6b2cb 100644 --- a/codex-rs/app-server/src/request_processors/thread_processor.rs +++ b/codex-rs/app-server/src/request_processors/thread_processor.rs @@ -96,21 +96,24 @@ fn collect_resume_override_mismatches( } } if let Some(requested_sandbox) = request.sandbox.as_ref() { - let active_sandbox = config_snapshot.sandbox_policy(); + let active_sandbox = thread_response_sandbox_policy( + &config_snapshot.permission_profile, + config_snapshot.cwd.as_path(), + ); let sandbox_matches = matches!( (requested_sandbox, &active_sandbox), ( SandboxMode::ReadOnly, - codex_protocol::protocol::SandboxPolicy::ReadOnly { .. } + codex_app_server_protocol::SandboxPolicy::ReadOnly { .. } ) | ( SandboxMode::WorkspaceWrite, - codex_protocol::protocol::SandboxPolicy::WorkspaceWrite { .. } + codex_app_server_protocol::SandboxPolicy::WorkspaceWrite { .. } ) | ( SandboxMode::DangerFullAccess, - codex_protocol::protocol::SandboxPolicy::DangerFullAccess + codex_app_server_protocol::SandboxPolicy::DangerFullAccess ) | ( SandboxMode::DangerFullAccess, - codex_protocol::protocol::SandboxPolicy::ExternalSandbox { .. } + codex_app_server_protocol::SandboxPolicy::ExternalSandbox { .. } ) ); if !sandbox_matches { diff --git a/codex-rs/app-server/src/request_processors/turn_processor.rs b/codex-rs/app-server/src/request_processors/turn_processor.rs index 2fbc64c398..244c950ae0 100644 --- a/codex-rs/app-server/src/request_processors/turn_processor.rs +++ b/codex-rs/app-server/src/request_processors/turn_processor.rs @@ -32,6 +32,26 @@ fn resolve_runtime_workspace_roots( resolved_roots } +fn permission_profile_from_legacy_sandbox_policy( + sandbox_policy: &codex_protocol::protocol::SandboxPolicy, + cwd: &AbsolutePathBuf, + current_permission_profile: &codex_protocol::models::PermissionProfile, +) -> codex_protocol::models::PermissionProfile { + let current_file_system_sandbox_policy = + current_permission_profile.file_system_sandbox_policy(); + let file_system_sandbox_policy = + codex_protocol::permissions::FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries( + sandbox_policy, + cwd.as_path(), + ¤t_file_system_sandbox_policy, + ); + codex_protocol::models::PermissionProfile::from_runtime_permissions_with_enforcement( + codex_protocol::models::SandboxEnforcement::from_legacy_sandbox_policy(sandbox_policy), + &file_system_sandbox_policy, + codex_protocol::permissions::NetworkSandboxPolicy::from(sandbox_policy), + ) +} + fn map_additional_context( additional_context: Option>, ) -> BTreeMap { @@ -524,7 +544,10 @@ impl TurnRequestProcessor { // `thread/settings/update` only acknowledges that the update was queued. // Clients that send dependent partial updates should wait for // `thread/settings/updated` or combine the fields in one request. - let snapshot = if permissions.is_some() || runtime_workspace_roots_request.is_some() { + let snapshot = if permissions.is_some() + || runtime_workspace_roots_request.is_some() + || sandbox_policy.is_some() + { Some(thread.config_snapshot().await) } else { None @@ -564,6 +587,26 @@ impl TurnRequestProcessor { let approvals_reviewer = approvals_reviewer.map(codex_app_server_protocol::ApprovalsReviewer::to_core); let sandbox_policy = sandbox_policy.map(|policy| policy.to_core()); + let sandbox_policy_permission_profile = if let Some(sandbox_policy) = + sandbox_policy.as_ref() + { + let Some(snapshot) = snapshot.as_ref() else { + return Err(internal_error(format!( + "{method} sandbox policy selection missing thread snapshot" + ))); + }; + let sandbox_policy_cwd = cwd + .as_ref() + .map(|cwd| AbsolutePathBuf::resolve_path_against_base(cwd, snapshot.cwd.as_path())) + .unwrap_or_else(|| snapshot.cwd.clone()); + Some(permission_profile_from_legacy_sandbox_policy( + sandbox_policy, + &sandbox_policy_cwd, + &snapshot.permission_profile, + )) + } else { + None + }; let (permission_profile, active_permission_profile, profile_workspace_roots) = if let Some(permissions) = permissions { let Some(snapshot) = snapshot.as_ref() else { @@ -623,8 +666,9 @@ impl TurnRequestProcessor { workspace_roots: runtime_workspace_roots.clone(), approval_policy, approvals_reviewer, - sandbox_policy: sandbox_policy.clone(), - permission_profile: permission_profile.clone(), + permission_profile: permission_profile + .clone() + .or_else(|| sandbox_policy_permission_profile.clone()), active_permission_profile: active_permission_profile.clone(), profile_workspace_roots: profile_workspace_roots.clone(), windows_sandbox_level: None, diff --git a/codex-rs/codex-mcp/src/runtime.rs b/codex-rs/codex-mcp/src/runtime.rs index 5404bf1eb5..5049dbd6e3 100644 --- a/codex-rs/codex-mcp/src/runtime.rs +++ b/codex-rs/codex-mcp/src/runtime.rs @@ -12,6 +12,8 @@ use std::time::Duration; use codex_exec_server::Environment; use codex_exec_server::EnvironmentManager; use codex_protocol::models::PermissionProfile; +use codex_protocol::permissions::FileSystemSandboxPolicy; +use codex_protocol::permissions::NetworkSandboxPolicy; use codex_protocol::protocol::SandboxPolicy; use serde::Deserialize; @@ -29,6 +31,30 @@ pub struct SandboxState { pub use_legacy_landlock: bool, } +impl SandboxState { + pub fn from_permission_profile( + permission_profile: PermissionProfile, + file_system_sandbox_policy: &FileSystemSandboxPolicy, + network_sandbox_policy: NetworkSandboxPolicy, + codex_linux_sandbox_exe: Option, + sandbox_cwd: PathBuf, + use_legacy_landlock: bool, + ) -> Self { + let sandbox_policy = permission_profile.compatibility_sandbox_policy( + file_system_sandbox_policy, + network_sandbox_policy, + sandbox_cwd.as_path(), + ); + Self { + permission_profile: Some(permission_profile), + sandbox_policy, + codex_linux_sandbox_exe, + sandbox_cwd, + use_legacy_landlock, + } + } +} + /// Runtime context used when resolving per-server MCP environments. /// /// `McpConfig` describes what servers exist. This value carries the canonical diff --git a/codex-rs/core/README.md b/codex-rs/core/README.md index 57b9e53f6b..3d6a269c38 100644 --- a/codex-rs/core/README.md +++ b/codex-rs/core/README.md @@ -14,8 +14,9 @@ When using the workspace-write sandbox policy, the Seatbelt profile allows writes under the configured writable roots while keeping `.git` (directory or pointer file), the resolved `gitdir:` target, and `.codex` read-only. -Network access and filesystem read/write roots are controlled by -`SandboxPolicy`. Seatbelt consumes the resolved policy and enforces it. +Network access and filesystem read/write roots are controlled by the resolved +permission profile. Seatbelt consumes the derived runtime policies and enforces +them. Seatbelt also keeps the legacy default preferences read access (`user-preference-read`) needed for cfprefs-backed macOS behavior. @@ -24,14 +25,15 @@ Seatbelt also keeps the legacy default preferences read access Expects the binary containing `codex-core` to run the equivalent of `codex sandbox` when `arg0` is `codex-linux-sandbox`. See the `codex-arg0` crate for details. -Legacy `SandboxPolicy` / `sandbox_mode` configs are still supported on Linux. -They can continue to use the legacy Landlock path when the split filesystem -policy is sandbox-equivalent to the legacy model after `cwd` resolution. +Legacy `sandbox_mode` configs are still supported on Linux at configuration and +protocol boundaries. They can continue to use the legacy Landlock path when the +split filesystem policy is sandbox-equivalent to the older mode model after +`cwd` resolution. Split filesystem policies that need direct `FileSystemSandboxPolicy` enforcement, such as read-only or denied carveouts under a broader writable root, automatically route through bubblewrap. The legacy Landlock path is used only when the split filesystem policy round-trips through the legacy -`SandboxPolicy` model without changing semantics. That includes overlapping +mode model without changing semantics. That includes overlapping cases like `/repo = write`, `/repo/a = none`, `/repo/a/b = write`, where the more specific writable child must reopen under a denied parent. @@ -50,14 +52,14 @@ bubblewrap path before invoking `bwrap`. ### Windows -Legacy `SandboxPolicy` / `sandbox_mode` configs are still supported on -Windows. Legacy `read-only` and `workspace-write` policies imply full +Legacy `sandbox_mode` configs are still supported on Windows at configuration +and protocol boundaries. Legacy `read-only` and `workspace-write` modes imply full filesystem read access; exact readable roots are represented by split filesystem policies instead. The elevated Windows sandbox also supports: -- legacy `ReadOnly` and `WorkspaceWrite` behavior +- legacy `read-only` and `workspace-write` behavior - split filesystem policies that need exact readable roots, exact writable roots, or extra read-only carveouts under writable roots - backend-managed system read roots required for basic execution, such as @@ -65,14 +67,14 @@ The elevated Windows sandbox also supports: `C:\ProgramData`, when a split filesystem policy requests platform defaults The unelevated restricted-token backend still supports the legacy full-read -Windows model for legacy `ReadOnly` and `WorkspaceWrite` behavior. It also +Windows model for legacy `read-only` and `workspace-write` behavior. It also supports a narrow split-filesystem subset: full-read split policies whose -writable roots still match the legacy `WorkspaceWrite` root set, but add extra +writable roots still match the legacy workspace-write root set, but add extra read-only carveouts under those writable roots. New `[permissions]` / split filesystem policies remain supported on Windows only when they can be enforced directly by the selected Windows backend or -round-trip through the legacy `SandboxPolicy` model without changing semantics. +round-trip through the legacy mode model without changing semantics. Policies that would require direct explicit unreadable carveouts (`none`) or reopened writable descendants under read-only carveouts still fail closed instead of running with weaker enforcement. diff --git a/codex-rs/core/src/agent/role_tests.rs b/codex-rs/core/src/agent/role_tests.rs index 5461323a36..073b9be046 100644 --- a/codex-rs/core/src/agent/role_tests.rs +++ b/codex-rs/core/src/agent/role_tests.rs @@ -337,7 +337,14 @@ writable_roots = ["./sandbox-root"] false ); - match &config.legacy_sandbox_policy() { + let permission_profile = config.permissions.effective_permission_profile(); + let file_system_policy = permission_profile.file_system_sandbox_policy(); + let sandbox_policy = permission_profile.compatibility_sandbox_policy( + &file_system_policy, + permission_profile.network_sandbox_policy(), + config.cwd.as_path(), + ); + match &sandbox_policy { SandboxPolicy::WorkspaceWrite { network_access, .. } => { assert_eq!(*network_access, true); } diff --git a/codex-rs/core/src/codex_thread.rs b/codex-rs/core/src/codex_thread.rs index dec1ce6895..12e0dcf3f2 100644 --- a/codex-rs/core/src/codex_thread.rs +++ b/codex-rs/core/src/codex_thread.rs @@ -24,7 +24,6 @@ use codex_protocol::protocol::AdditionalContextEntry; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::Event; use codex_protocol::protocol::Op; -use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::SessionConfiguredEvent; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::Submission; @@ -71,18 +70,6 @@ pub struct ThreadConfigSnapshot { pub thread_source: Option, } -impl ThreadConfigSnapshot { - pub fn sandbox_policy(&self) -> SandboxPolicy { - let file_system_sandbox_policy = self.permission_profile.file_system_sandbox_policy(); - codex_sandboxing::compatibility_sandbox_policy_for_permission_profile( - &self.permission_profile, - &file_system_sandbox_policy, - self.permission_profile.network_sandbox_policy(), - self.cwd.as_path(), - ) - } -} - /// Thread settings overrides that app-server validates before starting a turn. #[derive(Clone, Default)] pub struct CodexThreadSettingsOverrides { @@ -91,7 +78,6 @@ pub struct CodexThreadSettingsOverrides { pub profile_workspace_roots: Option>, pub approval_policy: Option, pub approvals_reviewer: Option, - pub sandbox_policy: Option, pub permission_profile: Option, pub active_permission_profile: Option, pub windows_sandbox_level: Option, @@ -310,7 +296,6 @@ impl CodexThread { profile_workspace_roots, approval_policy, approvals_reviewer, - sandbox_policy, permission_profile, active_permission_profile, windows_sandbox_level, @@ -337,7 +322,6 @@ impl CodexThread { profile_workspace_roots, approval_policy, approvals_reviewer, - sandbox_policy, permission_profile, active_permission_profile, windows_sandbox_level, diff --git a/codex-rs/core/src/config/config_loader_tests.rs b/codex-rs/core/src/config/config_loader_tests.rs index d0097d7537..141661ac0b 100644 --- a/codex-rs/core/src/config/config_loader_tests.rs +++ b/codex-rs/core/src/config/config_loader_tests.rs @@ -39,6 +39,22 @@ use std::path::Path; use tempfile::tempdir; use toml::Value as TomlValue; +trait LegacySandboxPolicyForTests { + fn legacy_sandbox_policy(&self) -> codex_protocol::protocol::SandboxPolicy; +} + +impl LegacySandboxPolicyForTests for crate::config::Config { + fn legacy_sandbox_policy(&self) -> codex_protocol::protocol::SandboxPolicy { + let permission_profile = self.permissions.effective_permission_profile(); + let file_system_policy = permission_profile.file_system_sandbox_policy(); + permission_profile.compatibility_sandbox_policy( + &file_system_policy, + permission_profile.network_sandbox_policy(), + self.cwd.as_path(), + ) + } +} + fn config_error_from_io(err: &std::io::Error) -> &ConfigError { err.get_ref() .and_then(|err| err.downcast_ref::()) diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index fa024c0bc3..4b7de40e7e 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -109,6 +109,22 @@ use std::path::Path; use std::time::Duration; use tempfile::TempDir; +trait LegacySandboxPolicyForTests { + fn legacy_sandbox_policy(&self) -> SandboxPolicy; +} + +impl LegacySandboxPolicyForTests for Config { + fn legacy_sandbox_policy(&self) -> SandboxPolicy { + let permission_profile = self.permissions.effective_permission_profile(); + let file_system_policy = permission_profile.file_system_sandbox_policy(); + permission_profile.compatibility_sandbox_policy( + &file_system_policy, + permission_profile.network_sandbox_policy(), + self.cwd.as_path(), + ) + } +} + fn stdio_mcp(command: &str) -> McpServerConfig { McpServerConfig { transport: McpServerTransportConfig::Stdio { diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 9624f42eb5..3b8bc77d63 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -93,13 +93,11 @@ use codex_protocol::config_types::WebSearchMode; use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::ActivePermissionProfile; use codex_protocol::models::PermissionProfile; -use codex_protocol::models::SandboxEnforcement; use codex_protocol::openai_models::ModelsResponse; use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::permissions::FileSystemSandboxPolicy; use codex_protocol::permissions::NetworkSandboxPolicy; use codex_protocol::protocol::AskForApproval; -use codex_protocol::protocol::SandboxPolicy; use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_absolute_path::AbsolutePathBufGuard; use rmcp::model::ElicitationCapability; @@ -147,7 +145,6 @@ pub use codex_config::ConstraintError; pub use codex_config::ConstraintResult; pub use codex_config::LoaderOverrides; pub use codex_network_proxy::NetworkProxyAuditMetadata; -use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile; pub use codex_sandboxing::system_bwrap_warning; pub use managed_features::ManagedFeatures; pub use network_proxy_spec::NetworkProxySpec; @@ -402,79 +399,6 @@ impl Permissions { self.permission_profile().network_sandbox_policy() } - /// Legacy compatibility projection derived from the canonical profile. - pub fn legacy_sandbox_policy(&self, cwd: &Path) -> SandboxPolicy { - let permission_profile = self.materialized_permission_profile(); - let file_system_sandbox_policy = permission_profile.file_system_sandbox_policy(); - compatibility_sandbox_policy_for_permission_profile( - &permission_profile, - &file_system_sandbox_policy, - permission_profile.network_sandbox_policy(), - cwd, - ) - } - - /// Check whether a legacy sandbox policy can be applied to this permission - /// set after projecting it into the canonical permission profile. - pub fn can_set_legacy_sandbox_policy( - &self, - sandbox_policy: &SandboxPolicy, - cwd: &Path, - ) -> ConstraintResult<()> { - let file_system_sandbox_policy = - FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(sandbox_policy, cwd); - let network_sandbox_policy = NetworkSandboxPolicy::from(sandbox_policy); - let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement( - SandboxEnforcement::from_legacy_sandbox_policy(sandbox_policy), - &file_system_sandbox_policy, - network_sandbox_policy, - ); - self.permission_profile_state - .can_set_legacy_permission_profile(&permission_profile) - } - - /// Set permissions from a legacy sandbox policy and keep every permission - /// projection in sync. - pub fn set_legacy_sandbox_policy( - &mut self, - sandbox_policy: SandboxPolicy, - cwd: &Path, - ) -> ConstraintResult<()> { - self.can_set_legacy_sandbox_policy(&sandbox_policy, cwd)?; - let file_system_sandbox_policy = - FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&sandbox_policy, cwd); - let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy); - let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement( - SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy), - &file_system_sandbox_policy, - network_sandbox_policy, - ); - self.workspace_roots = match &sandbox_policy { - SandboxPolicy::WorkspaceWrite { writable_roots, .. } => { - let mut workspace_roots = vec![ - AbsolutePathBuf::from_absolute_path(cwd) - .unwrap_or_else(|_| AbsolutePathBuf::resolve_path_against_base(cwd, "/")), - ]; - for root in writable_roots { - if !workspace_roots.iter().any(|existing| existing == root) { - workspace_roots.push(root.clone()); - } - } - workspace_roots - } - SandboxPolicy::DangerFullAccess - | SandboxPolicy::ExternalSandbox { .. } - | SandboxPolicy::ReadOnly { .. } => vec![ - AbsolutePathBuf::from_absolute_path(cwd) - .unwrap_or_else(|_| AbsolutePathBuf::resolve_path_against_base(cwd, "/")), - ], - }; - - self.permission_profile_state - .set_legacy_permission_profile(permission_profile)?; - Ok(()) - } - /// Set permissions from the canonical profile. pub fn set_permission_profile( &mut self, @@ -1239,24 +1163,6 @@ 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.workspace_roots_explicit = matches!( - &sandbox_policy, - SandboxPolicy::WorkspaceWrite { writable_roots, .. } if !writable_roots.is_empty() - ); - self.permissions - .set_legacy_sandbox_policy(sandbox_policy, self.cwd.as_path())?; - self.workspace_roots = self.permissions.workspace_roots().to_vec(); - Ok(()) - } - pub fn effective_workspace_roots(&self) -> Vec { let mut workspace_roots = self.workspace_roots.clone(); workspace_roots.extend(self.permissions.profile_workspace_roots().iter().cloned()); @@ -2846,7 +2752,7 @@ impl Config { // should still flow through the canonical profile representation. // Derive the old `sandbox_mode` defaults as a profile first, then // keep a legacy-compatible projection only for the remaining code - // paths that still speak `SandboxPolicy`. + // paths that still speak `sandbox_mode`. let mut permission_profile = cfg .derive_permission_profile( sandbox_mode, @@ -2857,7 +2763,7 @@ impl Config { ) .await; // The legacy-derived profiles above are expected to be - // representable as `SandboxPolicy`. This guard keeps the old safe + // representable as a legacy sandbox mode. This guard keeps the old safe // fallback behavior if future changes make this branch derive a // profile with split-only filesystem semantics, such as root write // with carveouts or writes that are not expressible as diff --git a/codex-rs/core/src/exec.rs b/codex-rs/core/src/exec.rs index e050552246..832c779c07 100644 --- a/codex-rs/core/src/exec.rs +++ b/codex-rs/core/src/exec.rs @@ -38,7 +38,6 @@ use codex_protocol::protocol::Event; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::ExecCommandOutputDeltaEvent; use codex_protocol::protocol::ExecOutputStream; -use codex_protocol::protocol::SandboxPolicy; use codex_sandboxing::SandboxCommand; use codex_sandboxing::SandboxManager; use codex_sandboxing::SandboxTransformRequest; @@ -419,11 +418,10 @@ pub fn build_exec_request( exec_req.windows_sandbox_level, exec_req.network.is_some(), ); - let sandbox_policy = exec_req.compatibility_sandbox_policy(); exec_req.windows_sandbox_filesystem_overrides = if use_windows_elevated_backend { resolve_windows_elevated_filesystem_overrides( exec_req.sandbox, - &sandbox_policy, + &exec_req.permission_profile, &exec_req.file_system_sandbox_policy, exec_req.network_sandbox_policy, sandbox_cwd, @@ -432,7 +430,7 @@ pub fn build_exec_request( } else { resolve_windows_restricted_token_filesystem_overrides( exec_req.sandbox, - &sandbox_policy, + &exec_req.permission_profile, &exec_req.file_system_sandbox_policy, exec_req.network_sandbox_policy, sandbox_cwd, @@ -1006,21 +1004,16 @@ async fn exec( #[cfg_attr(not(target_os = "windows"), allow(dead_code))] fn should_use_windows_restricted_token_sandbox( sandbox: SandboxType, - sandbox_policy: &SandboxPolicy, file_system_sandbox_policy: &FileSystemSandboxPolicy, ) -> bool { sandbox == SandboxType::WindowsRestrictedToken && file_system_sandbox_policy.kind == FileSystemSandboxKind::Restricted - && !matches!( - sandbox_policy, - SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } - ) } #[cfg_attr(not(test), allow(dead_code))] pub(crate) fn unsupported_windows_restricted_token_sandbox_reason( sandbox: SandboxType, - sandbox_policy: &SandboxPolicy, + permission_profile: &PermissionProfile, file_system_sandbox_policy: &FileSystemSandboxPolicy, network_sandbox_policy: NetworkSandboxPolicy, sandbox_policy_cwd: &AbsolutePathBuf, @@ -1029,7 +1022,7 @@ pub(crate) fn unsupported_windows_restricted_token_sandbox_reason( if windows_sandbox_level == WindowsSandboxLevel::Elevated { resolve_windows_elevated_filesystem_overrides( sandbox, - sandbox_policy, + permission_profile, file_system_sandbox_policy, network_sandbox_policy, sandbox_policy_cwd, @@ -1039,7 +1032,7 @@ pub(crate) fn unsupported_windows_restricted_token_sandbox_reason( } else { resolve_windows_restricted_token_filesystem_overrides( sandbox, - sandbox_policy, + permission_profile, file_system_sandbox_policy, network_sandbox_policy, sandbox_policy_cwd, @@ -1051,7 +1044,7 @@ pub(crate) fn unsupported_windows_restricted_token_sandbox_reason( pub(crate) fn resolve_windows_restricted_token_filesystem_overrides( sandbox: SandboxType, - sandbox_policy: &SandboxPolicy, + permission_profile: &PermissionProfile, file_system_sandbox_policy: &FileSystemSandboxPolicy, network_sandbox_policy: NetworkSandboxPolicy, sandbox_policy_cwd: &AbsolutePathBuf, @@ -1066,22 +1059,15 @@ pub(crate) fn resolve_windows_restricted_token_filesystem_overrides( let needs_direct_runtime_enforcement = file_system_sandbox_policy .needs_direct_runtime_enforcement(network_sandbox_policy, sandbox_policy_cwd); - if should_use_windows_restricted_token_sandbox( - sandbox, - sandbox_policy, - file_system_sandbox_policy, - ) && !needs_direct_runtime_enforcement + if should_use_windows_restricted_token_sandbox(sandbox, file_system_sandbox_policy) + && !needs_direct_runtime_enforcement { return Ok(None); } - if !should_use_windows_restricted_token_sandbox( - sandbox, - sandbox_policy, - file_system_sandbox_policy, - ) { + if !should_use_windows_restricted_token_sandbox(sandbox, file_system_sandbox_policy) { return Err(format!( - "windows sandbox backend cannot enforce file_system={:?}, network={network_sandbox_policy:?}, legacy_policy={sandbox_policy:?}; refusing to run unsandboxed", + "windows sandbox backend cannot enforce file_system={:?}, network={network_sandbox_policy:?}, permission_profile={permission_profile:?}; refusing to run unsandboxed", file_system_sandbox_policy.kind, )); } @@ -1108,6 +1094,11 @@ pub(crate) fn resolve_windows_restricted_token_filesystem_overrides( ); } + let sandbox_policy = permission_profile.compatibility_sandbox_policy( + file_system_sandbox_policy, + network_sandbox_policy, + sandbox_policy_cwd.as_path(), + ); let legacy_writable_roots = sandbox_policy.get_writable_roots_with_cwd(sandbox_policy_cwd); let split_writable_roots = file_system_sandbox_policy.get_writable_roots_with_cwd(sandbox_policy_cwd); @@ -1204,7 +1195,7 @@ fn windows_policy_has_root_read_access( pub(crate) fn resolve_windows_elevated_filesystem_overrides( sandbox: SandboxType, - sandbox_policy: &SandboxPolicy, + permission_profile: &PermissionProfile, file_system_sandbox_policy: &FileSystemSandboxPolicy, network_sandbox_policy: NetworkSandboxPolicy, sandbox_policy_cwd: &AbsolutePathBuf, @@ -1214,13 +1205,9 @@ pub(crate) fn resolve_windows_elevated_filesystem_overrides( return Ok(None); } - if !should_use_windows_restricted_token_sandbox( - sandbox, - sandbox_policy, - file_system_sandbox_policy, - ) { + if !should_use_windows_restricted_token_sandbox(sandbox, file_system_sandbox_policy) { return Err(format!( - "windows sandbox backend cannot enforce file_system={:?}, network={network_sandbox_policy:?}, legacy_policy={sandbox_policy:?}; refusing to run unsandboxed", + "windows sandbox backend cannot enforce file_system={:?}, network={network_sandbox_policy:?}, permission_profile={permission_profile:?}; refusing to run unsandboxed", file_system_sandbox_policy.kind, )); } @@ -1242,6 +1229,11 @@ pub(crate) fn resolve_windows_elevated_filesystem_overrides( let needs_direct_runtime_enforcement = file_system_sandbox_policy .needs_direct_runtime_enforcement(network_sandbox_policy, sandbox_policy_cwd); let normalize_path = |path: PathBuf| dunce::canonicalize(&path).unwrap_or(path); + let sandbox_policy = permission_profile.compatibility_sandbox_policy( + file_system_sandbox_policy, + network_sandbox_policy, + sandbox_policy_cwd.as_path(), + ); let legacy_writable_roots = sandbox_policy.get_writable_roots_with_cwd(sandbox_policy_cwd); let legacy_root_paths: BTreeSet = legacy_writable_roots .iter() diff --git a/codex-rs/core/src/exec_tests.rs b/codex-rs/core/src/exec_tests.rs index 1c71d02651..7a43bf1f7d 100644 --- a/codex-rs/core/src/exec_tests.rs +++ b/codex-rs/core/src/exec_tests.rs @@ -1,6 +1,8 @@ use super::*; use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::PermissionProfile; +use codex_protocol::models::SandboxEnforcement; +use codex_protocol::protocol::SandboxPolicy; use codex_sandboxing::SandboxType; use core_test_support::PathBufExt; use core_test_support::PathExt; @@ -26,6 +28,17 @@ fn make_exec_output( } } +fn permission_profile_for_runtime_permissions( + policy: &SandboxPolicy, + file_system_policy: &FileSystemSandboxPolicy, +) -> PermissionProfile { + PermissionProfile::from_runtime_permissions_with_enforcement( + SandboxEnforcement::from_legacy_sandbox_policy(policy), + file_system_policy, + NetworkSandboxPolicy::from(policy), + ) +} + #[test] fn sandbox_detection_requires_keywords() { let output = make_exec_output(/*exit_code*/ 1, "", "", ""); @@ -387,7 +400,6 @@ fn windows_restricted_token_skips_external_sandbox_policies() { assert_eq!( should_use_windows_restricted_token_sandbox( SandboxType::WindowsRestrictedToken, - &policy, &file_system_policy, ), false @@ -402,7 +414,6 @@ fn windows_restricted_token_runs_for_legacy_restricted_policies() { assert_eq!( should_use_windows_restricted_token_sandbox( SandboxType::WindowsRestrictedToken, - &policy, &file_system_policy, ), true @@ -431,19 +442,21 @@ fn windows_restricted_token_rejects_network_only_restrictions() { network_access: codex_protocol::protocol::NetworkAccess::Restricted, }; let file_system_policy = FileSystemSandboxPolicy::unrestricted(); + let permission_profile = + permission_profile_for_runtime_permissions(&policy, &file_system_policy); let sandbox_policy_cwd = AbsolutePathBuf::current_dir().expect("cwd"); assert_eq!( unsupported_windows_restricted_token_sandbox_reason( SandboxType::WindowsRestrictedToken, - &policy, + &permission_profile, &file_system_policy, NetworkSandboxPolicy::Restricted, &sandbox_policy_cwd, WindowsSandboxLevel::RestrictedToken, ), Some( - "windows sandbox backend cannot enforce file_system=Unrestricted, network=Restricted, legacy_policy=ExternalSandbox { network_access: Restricted }; refusing to run unsandboxed".to_string() + "windows sandbox backend cannot enforce file_system=Unrestricted, network=Restricted, permission_profile=Managed { file_system: Unrestricted, network: Restricted }; refusing to run unsandboxed".to_string() ) ); } @@ -452,12 +465,14 @@ fn windows_restricted_token_rejects_network_only_restrictions() { fn windows_restricted_token_allows_legacy_restricted_policies() { let policy = SandboxPolicy::new_read_only_policy(); let file_system_policy = FileSystemSandboxPolicy::from(&policy); + let permission_profile = + permission_profile_for_runtime_permissions(&policy, &file_system_policy); let sandbox_policy_cwd = AbsolutePathBuf::current_dir().expect("cwd"); assert_eq!( unsupported_windows_restricted_token_sandbox_reason( SandboxType::WindowsRestrictedToken, - &policy, + &permission_profile, &file_system_policy, NetworkSandboxPolicy::Restricted, &sandbox_policy_cwd, @@ -476,12 +491,14 @@ fn windows_restricted_token_allows_legacy_workspace_write_policies() { exclude_slash_tmp: true, }; let file_system_policy = FileSystemSandboxPolicy::from(&policy); + let permission_profile = + permission_profile_for_runtime_permissions(&policy, &file_system_policy); let sandbox_policy_cwd = AbsolutePathBuf::current_dir().expect("cwd"); assert_eq!( unsupported_windows_restricted_token_sandbox_reason( SandboxType::WindowsRestrictedToken, - &policy, + &permission_profile, &file_system_policy, NetworkSandboxPolicy::Restricted, &sandbox_policy_cwd, @@ -508,11 +525,13 @@ fn windows_elevated_allows_split_restricted_read_policies() { access: codex_protocol::permissions::FileSystemAccessMode::Read, }, ]); + let permission_profile = + permission_profile_for_runtime_permissions(&policy, &file_system_policy); assert_eq!( unsupported_windows_restricted_token_sandbox_reason( SandboxType::WindowsRestrictedToken, - &policy, + &permission_profile, &file_system_policy, NetworkSandboxPolicy::Restricted, &temp_dir.path().abs(), @@ -550,11 +569,13 @@ fn windows_restricted_token_rejects_split_only_filesystem_policies() { access: codex_protocol::permissions::FileSystemAccessMode::Read, }, ]); + let permission_profile = + permission_profile_for_runtime_permissions(&policy, &file_system_policy); assert_eq!( unsupported_windows_restricted_token_sandbox_reason( SandboxType::WindowsRestrictedToken, - &policy, + &permission_profile, &file_system_policy, NetworkSandboxPolicy::Restricted, &temp_dir.path().abs(), @@ -593,11 +614,13 @@ fn windows_restricted_token_rejects_root_write_read_only_carveouts() { access: codex_protocol::permissions::FileSystemAccessMode::Read, }, ]); + let permission_profile = + permission_profile_for_runtime_permissions(&policy, &file_system_policy); assert_eq!( unsupported_windows_restricted_token_sandbox_reason( SandboxType::WindowsRestrictedToken, - &policy, + &permission_profile, &file_system_policy, NetworkSandboxPolicy::Restricted, &temp_dir.path().abs(), @@ -644,6 +667,8 @@ fn windows_restricted_token_supports_full_read_split_write_read_carveouts() { access: codex_protocol::permissions::FileSystemAccessMode::Read, }, ]); + let permission_profile = + permission_profile_for_runtime_permissions(&policy, &file_system_policy); // The legacy workspace-write root already protects top-level `.codex`, so // the restricted-token overlay only needs the extra read-only docs carveout. @@ -652,7 +677,7 @@ fn windows_restricted_token_supports_full_read_split_write_read_carveouts() { assert_eq!( resolve_windows_restricted_token_filesystem_overrides( SandboxType::WindowsRestrictedToken, - &policy, + &permission_profile, &file_system_policy, NetworkSandboxPolicy::Restricted, &cwd, @@ -702,11 +727,13 @@ fn windows_restricted_token_rejects_unreadable_split_carveouts() { access: codex_protocol::permissions::FileSystemAccessMode::Deny, }, ]); + let permission_profile = + permission_profile_for_runtime_permissions(&policy, &file_system_policy); assert_eq!( resolve_windows_restricted_token_filesystem_overrides( SandboxType::WindowsRestrictedToken, - &policy, + &permission_profile, &file_system_policy, NetworkSandboxPolicy::Restricted, &cwd, @@ -737,11 +764,13 @@ fn windows_elevated_supports_split_restricted_read_roots() { access: codex_protocol::permissions::FileSystemAccessMode::Read, }, ]); + let permission_profile = + permission_profile_for_runtime_permissions(&policy, &file_system_policy); assert_eq!( resolve_windows_elevated_filesystem_overrides( SandboxType::WindowsRestrictedToken, - &policy, + &permission_profile, &file_system_policy, NetworkSandboxPolicy::Restricted, &temp_dir.path().abs(), @@ -792,11 +821,13 @@ fn windows_elevated_supports_split_write_read_carveouts() { access: codex_protocol::permissions::FileSystemAccessMode::Read, }, ]); + let permission_profile = + permission_profile_for_runtime_permissions(&policy, &file_system_policy); assert_eq!( resolve_windows_elevated_filesystem_overrides( SandboxType::WindowsRestrictedToken, - &policy, + &permission_profile, &file_system_policy, NetworkSandboxPolicy::Restricted, &temp_dir.path().abs(), @@ -850,11 +881,13 @@ fn windows_elevated_supports_unreadable_split_carveouts() { access: codex_protocol::permissions::FileSystemAccessMode::Deny, }, ]); + let permission_profile = + permission_profile_for_runtime_permissions(&policy, &file_system_policy); assert_eq!( resolve_windows_elevated_filesystem_overrides( SandboxType::WindowsRestrictedToken, - &policy, + &permission_profile, &file_system_policy, NetworkSandboxPolicy::Restricted, &temp_dir.path().abs(), @@ -912,11 +945,13 @@ fn windows_elevated_supports_unreadable_globs() { access: codex_protocol::permissions::FileSystemAccessMode::Deny, }, ]); + let permission_profile = + permission_profile_for_runtime_permissions(&policy, &file_system_policy); assert_eq!( resolve_windows_elevated_filesystem_overrides( SandboxType::WindowsRestrictedToken, - &policy, + &permission_profile, &file_system_policy, NetworkSandboxPolicy::Restricted, &temp_dir.path().abs(), @@ -977,11 +1012,13 @@ fn windows_elevated_rejects_reopened_writable_descendants() { access: codex_protocol::permissions::FileSystemAccessMode::Write, }, ]); + let permission_profile = + permission_profile_for_runtime_permissions(&policy, &file_system_policy); assert_eq!( unsupported_windows_restricted_token_sandbox_reason( SandboxType::WindowsRestrictedToken, - &policy, + &permission_profile, &file_system_policy, NetworkSandboxPolicy::Restricted, &temp_dir.path().abs(), diff --git a/codex-rs/core/src/guardian/review_session.rs b/codex-rs/core/src/guardian/review_session.rs index fed87cd75f..17c977f98c 100644 --- a/codex-rs/core/src/guardian/review_session.rs +++ b/codex-rs/core/src/guardian/review_session.rs @@ -20,7 +20,6 @@ use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::InitialHistory; use codex_protocol::protocol::Op; use codex_protocol::protocol::RolloutItem; -use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::SubAgentSource; use codex_protocol::protocol::TokenUsage; @@ -719,9 +718,7 @@ async fn run_review_on_session( .total_token_usage() .await .unwrap_or_default(); - // The legacy SandboxPolicy should match the PermissionProfile. let guardian_permission_profile = PermissionProfile::read_only(); - let legacy_sandbox_policy = SandboxPolicy::new_read_only_policy(); let submit_result = run_before_review_deadline( deadline, @@ -736,7 +733,6 @@ async fn run_review_on_session( #[allow(deprecated)] cwd: Some(params.parent_turn.cwd.to_path_buf()), approval_policy: Some(AskForApproval::Never), - sandbox_policy: Some(legacy_sandbox_policy), permission_profile: Some(guardian_permission_profile), summary: Some(params.reasoning_summary), personality: params.personality, diff --git a/codex-rs/core/src/mcp_tool_call.rs b/codex-rs/core/src/mcp_tool_call.rs index e514fa0d03..a653d391ab 100644 --- a/codex-rs/core/src/mcp_tool_call.rs +++ b/codex-rs/core/src/mcp_tool_call.rs @@ -716,14 +716,16 @@ async fn augment_mcp_tool_request_meta_with_sandbox_state( return Ok(meta); } - let sandbox_state = serde_json::to_value(SandboxState { - permission_profile: Some(turn_context.permission_profile()), - sandbox_policy: turn_context.sandbox_policy(), - codex_linux_sandbox_exe: turn_context.codex_linux_sandbox_exe.clone(), - #[allow(deprecated)] - sandbox_cwd: turn_context.cwd.to_path_buf(), - use_legacy_landlock: turn_context.features.use_legacy_landlock(), - })?; + #[allow(deprecated)] + let sandbox_cwd = turn_context.cwd.to_path_buf(); + let sandbox_state = serde_json::to_value(SandboxState::from_permission_profile( + turn_context.permission_profile(), + &turn_context.file_system_sandbox_policy(), + turn_context.network_sandbox_policy(), + turn_context.codex_linux_sandbox_exe.clone(), + sandbox_cwd, + turn_context.features.use_legacy_landlock(), + ))?; match meta.as_mut() { Some(serde_json::Value::Object(map)) => { diff --git a/codex-rs/core/src/sandboxing/mod.rs b/codex-rs/core/src/sandboxing/mod.rs index f853ea3ba6..8a2a2849fd 100644 --- a/codex-rs/core/src/sandboxing/mod.rs +++ b/codex-rs/core/src/sandboxing/mod.rs @@ -22,10 +22,8 @@ use codex_protocol::models::PermissionProfile; pub use codex_protocol::models::SandboxPermissions; use codex_protocol::permissions::FileSystemSandboxPolicy; use codex_protocol::permissions::NetworkSandboxPolicy; -use codex_protocol::protocol::SandboxPolicy; use codex_sandboxing::SandboxExecRequest; use codex_sandboxing::SandboxType; -use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile; use codex_utils_absolute_path::AbsolutePathBuf; use std::collections::HashMap; @@ -102,15 +100,6 @@ impl ExecRequest { } } - pub(crate) fn compatibility_sandbox_policy(&self) -> SandboxPolicy { - compatibility_sandbox_policy_for_permission_profile( - &self.permission_profile, - &self.file_system_sandbox_policy, - self.network_sandbox_policy, - self.windows_sandbox_policy_cwd.as_path(), - ) - } - pub(crate) fn from_sandbox_exec_request( request: SandboxExecRequest, options: ExecOptions, diff --git a/codex-rs/core/src/session/handlers.rs b/codex-rs/core/src/session/handlers.rs index 7730a30ad7..701fcb9f82 100644 --- a/codex-rs/core/src/session/handlers.rs +++ b/codex-rs/core/src/session/handlers.rs @@ -15,6 +15,7 @@ use crate::session::session::Session; use crate::session::session::SessionSettingsUpdate; use crate::config::Config; +use crate::path_utils::normalize_for_native_workdir; use crate::realtime_context::REALTIME_TURN_TOKEN_BUDGET; use crate::realtime_context::truncate_realtime_text_to_token_budget; use crate::realtime_conversation::REALTIME_USER_TEXT_PREFIX; @@ -51,6 +52,7 @@ use codex_protocol::protocol::TurnAbortReason; use codex_protocol::protocol::WarningEvent; use codex_protocol::request_permissions::RequestPermissionsResponse; use codex_protocol::request_user_input::RequestUserInputResponse; +use codex_utils_absolute_path::AbsolutePathBuf; use crate::context_manager::is_user_turn_boundary; use codex_protocol::dynamic_tools::DynamicToolResponse; @@ -121,42 +123,65 @@ async fn thread_settings_update( sess: &Session, thread_settings: ThreadSettingsOverrides, ) -> SessionSettingsUpdate { + let (collaboration_mode, permission_profile) = { + let state = sess.state.lock().await; + let session_configuration = &state.session_configuration; + let collaboration_mode = match thread_settings.collaboration_mode.clone() { + Some(collaboration_mode) => collaboration_mode, + None => { + // Model and reasoning effort live in CollaborationMode settings today, so + // partial thread-settings updates refresh those fields on the active mode. + session_configuration.collaboration_mode.with_updates( + thread_settings.model.clone(), + thread_settings.effort, + /*developer_instructions*/ None, + ) + } + }; + let update_cwd = thread_settings + .cwd + .as_ref() + .map(|cwd| { + AbsolutePathBuf::relative_to_current_dir(normalize_for_native_workdir( + cwd.as_path(), + )) + .unwrap_or_else(|err| { + tracing::warn!("failed to normalize update cwd: {cwd:?}: {err}"); + session_configuration.cwd.clone() + }) + }) + .unwrap_or_else(|| session_configuration.cwd.clone()); + let current_file_system_sandbox_policy = session_configuration.file_system_sandbox_policy(); + let permission_profile = thread_settings.permission_profile_update_for_cwd( + update_cwd.as_path(), + Some(¤t_file_system_sandbox_policy), + ); + (collaboration_mode, permission_profile) + }; + let ThreadSettingsOverrides { cwd, workspace_roots, profile_workspace_roots, approval_policy, approvals_reviewer, - sandbox_policy, - permission_profile, + sandbox_policy: _, + permission_profile: _, active_permission_profile, windows_sandbox_level, - model, - effort, + model: _, + effort: _, summary, service_tier, - collaboration_mode, + collaboration_mode: _, personality, } = thread_settings; - let collaboration_mode = match collaboration_mode { - Some(collaboration_mode) => collaboration_mode, - None => { - let state = sess.state.lock().await; - // Model and reasoning effort live in CollaborationMode settings today, so - // partial thread-settings updates refresh those fields on the active mode. - state - .session_configuration - .collaboration_mode - .with_updates(model, effort, /*developer_instructions*/ None) - } - }; SessionSettingsUpdate { cwd, workspace_roots, profile_workspace_roots, approval_policy, approvals_reviewer, - sandbox_policy, permission_profile, active_permission_profile, windows_sandbox_level, diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 8c535cd728..a9980ddde8 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -349,7 +349,6 @@ use codex_protocol::protocol::Op; use codex_protocol::protocol::RateLimitSnapshot; use codex_protocol::protocol::RequestUserInputEvent; use codex_protocol::protocol::ReviewDecision; -use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::SessionConfiguredEvent; use codex_protocol::protocol::SessionNetworkProxyRuntime; use codex_protocol::protocol::StreamErrorEvent; diff --git a/codex-rs/core/src/session/rollout_reconstruction_tests.rs b/codex-rs/core/src/session/rollout_reconstruction_tests.rs index 05be3e8967..aba629d01d 100644 --- a/codex-rs/core/src/session/rollout_reconstruction_tests.rs +++ b/codex-rs/core/src/session/rollout_reconstruction_tests.rs @@ -9,9 +9,23 @@ use codex_protocol::protocol::CompactedItem; use codex_protocol::protocol::InitialHistory; use codex_protocol::protocol::InterAgentCommunication; use codex_protocol::protocol::ResumedHistory; +use codex_protocol::protocol::SandboxPolicy; use pretty_assertions::assert_eq; use std::path::PathBuf; +fn legacy_sandbox_policy_for_turn_context(turn_context: &TurnContext) -> SandboxPolicy { + let file_system_sandbox_policy = turn_context.file_system_sandbox_policy(); + #[allow(deprecated)] + let cwd = turn_context.cwd.as_path(); + turn_context + .permission_profile() + .compatibility_sandbox_policy( + &file_system_sandbox_policy, + turn_context.network_sandbox_policy(), + cwd, + ) +} + fn user_message(text: &str) -> ResponseItem { ResponseItem::Message { id: None, @@ -65,7 +79,7 @@ async fn record_initial_history_resumed_bare_turn_context_does_not_hydrate_previ current_date: turn_context.current_date.clone(), timezone: turn_context.timezone.clone(), approval_policy: turn_context.approval_policy.value(), - sandbox_policy: turn_context.sandbox_policy(), + sandbox_policy: legacy_sandbox_policy_for_turn_context(&turn_context), permission_profile: None, network: None, file_system_sandbox_policy: None, @@ -103,7 +117,7 @@ async fn record_initial_history_resumed_hydrates_previous_turn_settings_from_lif current_date: turn_context.current_date.clone(), timezone: turn_context.timezone.clone(), approval_policy: turn_context.approval_policy.value(), - sandbox_policy: turn_context.sandbox_policy(), + sandbox_policy: legacy_sandbox_policy_for_turn_context(&turn_context), permission_profile: None, network: None, file_system_sandbox_policy: None, @@ -952,7 +966,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis current_date: turn_context.current_date.clone(), timezone: turn_context.timezone.clone(), approval_policy: turn_context.approval_policy.value(), - sandbox_policy: turn_context.sandbox_policy(), + sandbox_policy: legacy_sandbox_policy_for_turn_context(&turn_context), permission_profile: None, network: None, file_system_sandbox_policy: None, @@ -1030,7 +1044,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis current_date: turn_context.current_date.clone(), timezone: turn_context.timezone.clone(), approval_policy: turn_context.approval_policy.value(), - sandbox_policy: turn_context.sandbox_policy(), + sandbox_policy: legacy_sandbox_policy_for_turn_context(&turn_context), permission_profile: None, network: None, file_system_sandbox_policy: None, @@ -1058,7 +1072,7 @@ async fn record_initial_history_resumed_aborted_turn_without_id_clears_active_tu current_date: turn_context.current_date.clone(), timezone: turn_context.timezone.clone(), approval_policy: turn_context.approval_policy.value(), - sandbox_policy: turn_context.sandbox_policy(), + sandbox_policy: legacy_sandbox_policy_for_turn_context(&turn_context), permission_profile: None, network: None, file_system_sandbox_policy: None, @@ -1176,7 +1190,7 @@ async fn record_initial_history_resumed_unmatched_abort_preserves_active_turn_fo current_date: turn_context.current_date.clone(), timezone: turn_context.timezone.clone(), approval_policy: turn_context.approval_policy.value(), - sandbox_policy: turn_context.sandbox_policy(), + sandbox_policy: legacy_sandbox_policy_for_turn_context(&turn_context), permission_profile: None, network: None, file_system_sandbox_policy: None, @@ -1293,7 +1307,7 @@ async fn record_initial_history_resumed_trailing_incomplete_turn_compaction_clea current_date: turn_context.current_date.clone(), timezone: turn_context.timezone.clone(), approval_policy: turn_context.approval_policy.value(), - sandbox_policy: turn_context.sandbox_policy(), + sandbox_policy: legacy_sandbox_policy_for_turn_context(&turn_context), permission_profile: None, network: None, file_system_sandbox_policy: None, @@ -1451,7 +1465,7 @@ async fn record_initial_history_resumed_replaced_incomplete_compacted_turn_clear current_date: turn_context.current_date.clone(), timezone: turn_context.timezone.clone(), approval_policy: turn_context.approval_policy.value(), - sandbox_policy: turn_context.sandbox_policy(), + sandbox_policy: legacy_sandbox_policy_for_turn_context(&turn_context), permission_profile: None, network: None, file_system_sandbox_policy: None, diff --git a/codex-rs/core/src/session/session.rs b/codex-rs/core/src/session/session.rs index c9e1807fe6..dc623df263 100644 --- a/codex-rs/core/src/session/session.rs +++ b/codex-rs/core/src/session/session.rs @@ -13,6 +13,15 @@ use codex_protocol::protocol::ThreadSource; use codex_protocol::protocol::TurnEnvironmentSelection; use tokio::sync::Semaphore; +fn sandbox_mode_label(permission_profile: &PermissionProfile) -> &'static str { + match codex_config::sandbox_mode_requirement_for_permission_profile(permission_profile) { + codex_config::SandboxModeRequirement::ReadOnly => "read-only", + codex_config::SandboxModeRequirement::WorkspaceWrite => "workspace-write", + codex_config::SandboxModeRequirement::DangerFullAccess => "danger-full-access", + codex_config::SandboxModeRequirement::ExternalSandbox => "external-sandbox", + } +} + /// Context for an initialized model agent /// /// A session has at most 1 running task at a time, and can be interrupted by user input. @@ -145,20 +154,6 @@ impl SessionConfiguration { .set_legacy_permission_profile(permission_profile) } - pub(super) fn sandbox_policy(&self) -> SandboxPolicy { - self.permission_profile() - .to_legacy_sandbox_policy(&self.cwd) - .unwrap_or_else(|_| { - let file_system_sandbox_policy = self.file_system_sandbox_policy(); - codex_sandboxing::compatibility_sandbox_policy_for_permission_profile( - self.permission_profile_state.permission_profile(), - &file_system_sandbox_policy, - self.network_sandbox_policy(), - &self.cwd, - ) - }) - } - pub(super) fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy { self.permission_profile().file_system_sandbox_policy() } @@ -193,9 +188,20 @@ impl SessionConfiguration { pub(crate) fn apply(&self, updates: &SessionSettingsUpdate) -> ConstraintResult { let mut next_configuration = self.clone(); - let current_sandbox_policy = self.sandbox_policy(); let current_file_system_sandbox_policy = self.file_system_sandbox_policy(); let current_network_sandbox_policy = self.network_sandbox_policy(); + let current_sandbox_policy = self + .permission_profile() + .to_legacy_sandbox_policy(&self.cwd) + .unwrap_or_else(|_| { + self.permission_profile_state + .permission_profile() + .compatibility_sandbox_policy( + ¤t_file_system_sandbox_policy, + current_network_sandbox_policy, + &self.cwd, + ) + }); let legacy_file_system_projection = FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries( ¤t_sandbox_policy, @@ -325,23 +331,6 @@ impl SessionConfiguration { )?; next_configuration.original_config_do_not_use = Arc::new(config); } - } else if let Some(sandbox_policy) = updates.sandbox_policy.clone() { - let file_system_sandbox_policy = - FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries( - &sandbox_policy, - &next_configuration.cwd, - ¤t_file_system_sandbox_policy, - ); - let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy); - next_configuration - .permission_profile_state - .set_legacy_permission_profile( - PermissionProfile::from_runtime_permissions_with_enforcement( - SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy), - &file_system_sandbox_policy, - network_sandbox_policy, - ), - )?; } else if cwd_changed && file_system_policy_matches_legacy && file_system_policy_has_rebindable_project_root_write @@ -418,7 +407,6 @@ pub(crate) struct SessionSettingsUpdate { pub(crate) profile_workspace_roots: Option>, pub(crate) approval_policy: Option, pub(crate) approvals_reviewer: Option, - pub(crate) sandbox_policy: Option, pub(crate) permission_profile: Option, pub(crate) active_permission_profile: Option, pub(crate) windows_sandbox_level: Option, @@ -701,7 +689,8 @@ impl Session { model: session_configuration.collaboration_mode.model().to_string(), provider_name: config.model_provider_id.clone(), approval_policy: session_configuration.approval_policy.value().to_string(), - sandbox_policy: format!("{:?}", session_configuration.sandbox_policy()), + sandbox_policy: sandbox_mode_label(&session_configuration.permission_profile()) + .to_string(), }; let rollout_thread_trace = if matches!( session_configuration.session_source, @@ -817,9 +806,7 @@ impl Session { config.model_context_window, config.model_auto_compact_token_limit, config.permissions.approval_policy.value(), - config - .permissions - .legacy_sandbox_policy(session_configuration.cwd.as_path()), + sandbox_mode_label(&session_configuration.permission_profile()), mcp_servers.keys().map(String::as_str).collect(), ); diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index aa2586cae4..00a25fde8b 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -68,6 +68,32 @@ use crate::tasks::SessionTask; use crate::tasks::SessionTaskContext; use crate::tasks::UserShellCommandMode; use crate::tasks::execute_user_shell_command; + +fn legacy_sandbox_policy_for_turn_context(turn_context: &TurnContext) -> SandboxPolicy { + let file_system_sandbox_policy = turn_context.file_system_sandbox_policy(); + #[allow(deprecated)] + let cwd = turn_context.cwd.as_path(); + turn_context + .permission_profile() + .compatibility_sandbox_policy( + &file_system_sandbox_policy, + turn_context.network_sandbox_policy(), + cwd, + ) +} + +fn legacy_sandbox_policy_for_session_configuration( + session_configuration: &SessionConfiguration, +) -> SandboxPolicy { + let file_system_sandbox_policy = session_configuration.file_system_sandbox_policy(); + session_configuration + .permission_profile() + .compatibility_sandbox_policy( + &file_system_sandbox_policy, + session_configuration.network_sandbox_policy(), + session_configuration.cwd.as_path(), + ) +} use crate::tools::ToolRouter; use crate::tools::context::ToolInvocation; use crate::tools::context::ToolPayload; @@ -912,7 +938,7 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow .new_turn_with_sub_id( "sandbox-policy-change".to_string(), SessionSettingsUpdate { - sandbox_policy: Some(SandboxPolicy::DangerFullAccess), + permission_profile: Some(PermissionProfile::Disabled), ..Default::default() }, ) @@ -2266,9 +2292,6 @@ async fn record_initial_history_reconstructs_forked_transcript() { async fn session_configured_reports_permission_profile_for_external_sandbox() -> anyhow::Result<()> { let server = start_mock_server().await; - let sandbox_policy = SandboxPolicy::ExternalSandbox { - network_access: codex_protocol::protocol::NetworkAccess::Restricted, - }; let permission_profile = PermissionProfile::External { network: NetworkSandboxPolicy::Restricted, }; @@ -2276,11 +2299,8 @@ async fn session_configured_reports_permission_profile_for_external_sandbox() -> let mut builder = test_codex().with_config(move |config| { config .permissions - .set_permission_profile(permission_profile.clone()) + .set_permission_profile(permission_profile) .expect("set permission profile"); - config - .set_legacy_sandbox_policy(sandbox_policy) - .expect("set sandbox policy"); }); let test = builder.build(&server).await?; @@ -2464,7 +2484,7 @@ async fn record_initial_history_forked_hydrates_previous_turn_settings() { current_date: turn_context.current_date.clone(), timezone: turn_context.timezone.clone(), approval_policy: turn_context.approval_policy.value(), - sandbox_policy: turn_context.sandbox_policy(), + sandbox_policy: legacy_sandbox_policy_for_turn_context(&turn_context), permission_profile: None, network: None, file_system_sandbox_policy: None, @@ -3897,7 +3917,7 @@ async fn session_configuration_apply_permission_profile_accepts_direct_write_roo file_system_sandbox_policy ); assert_eq!( - updated.sandbox_policy(), + legacy_sandbox_policy_for_session_configuration(&updated), SandboxPolicy::WorkspaceWrite { writable_roots: vec![external_write_path], network_access: false, @@ -5572,7 +5592,7 @@ async fn user_turn_updates_approvals_reviewer() { cwd: Some(config.cwd.to_path_buf()), approval_policy: Some(config.permissions.approval_policy.value()), approvals_reviewer: Some(codex_config::types::ApprovalsReviewer::AutoReview), - sandbox_policy: Some(config.legacy_sandbox_policy()), + permission_profile: Some(config.permissions.effective_permission_profile()), summary: config.model_reasoning_summary, personality: config.personality, collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { @@ -7555,7 +7575,7 @@ async fn build_initial_context_restates_realtime_start_when_reference_context_is fn file_system_policy_with_unreadable_glob(turn_context: &TurnContext) -> FileSystemSandboxPolicy { #[allow(deprecated)] let mut policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd( - &turn_context.sandbox_policy(), + &legacy_sandbox_policy_for_turn_context(turn_context), &turn_context.cwd, ); #[allow(deprecated)] @@ -10329,11 +10349,10 @@ async fn rejects_escalated_permissions_when_policy_not_on_request() { #[tokio::test] async fn shell_tool_cancellation_waits_for_runtime_cleanup() -> anyhow::Result<()> { let session = make_session_with_config(|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"); }) .await?; let turn_context = session.new_default_turn().await; diff --git a/codex-rs/core/src/session/turn.rs b/codex-rs/core/src/session/turn.rs index f808134f99..c0be708c20 100644 --- a/codex-rs/core/src/session/turn.rs +++ b/codex-rs/core/src/session/turn.rs @@ -1710,7 +1710,7 @@ async fn try_run_sampling_request( feedback_tags!( model = turn_context.model_info.slug.clone(), approval_policy = turn_context.approval_policy.value(), - sandbox_policy = &turn_context.sandbox_policy(), + permission_profile = turn_context.permission_profile(), effort = turn_context.reasoning_effort, auth_mode = sess.services.auth_manager.auth_mode(), features = sess.features.enabled_features(), diff --git a/codex-rs/core/src/session/turn_context.rs b/codex-rs/core/src/session/turn_context.rs index 4d1c27e49c..cf170007cc 100644 --- a/codex-rs/core/src/session/turn_context.rs +++ b/codex-rs/core/src/session/turn_context.rs @@ -9,7 +9,6 @@ use codex_protocol::models::AdditionalPermissionProfile; use codex_protocol::openai_models::ToolMode; use codex_protocol::protocol::ThreadSource; use codex_protocol::protocol::TurnEnvironmentSelection; -use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile; use codex_sandboxing::policy_transforms::effective_file_system_sandbox_policy; use codex_sandboxing::policy_transforms::effective_network_sandbox_policy; use std::sync::atomic::AtomicBool; @@ -112,18 +111,6 @@ impl TurnContext { self.permission_profile.network_sandbox_policy() } - pub(crate) fn sandbox_policy(&self) -> SandboxPolicy { - let file_system_sandbox_policy = self.file_system_sandbox_policy(); - let network_sandbox_policy = self.network_sandbox_policy(); - compatibility_sandbox_policy_for_permission_profile( - &self.permission_profile, - &file_system_sandbox_policy, - network_sandbox_policy, - #[allow(deprecated)] - &self.cwd, - ) - } - pub(crate) fn effective_reasoning_effort(&self) -> Option { if self.model_info.supports_reasoning_summaries { self.reasoning_effort @@ -316,13 +303,20 @@ impl TurnContext { // the legacy sandbox policy. This keeps turn-context payloads stable // while both fields exist; once callers consume only the split policy, // this comparison and the legacy projection should go away. + let file_system_sandbox_policy = self.file_system_sandbox_policy(); + let network_sandbox_policy = self.network_sandbox_policy(); + let sandbox_policy = self.permission_profile.compatibility_sandbox_policy( + &file_system_sandbox_policy, + network_sandbox_policy, + #[allow(deprecated)] + &self.cwd, + ); let legacy_file_system_sandbox_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd( - &self.sandbox_policy(), + &sandbox_policy, #[allow(deprecated)] &self.cwd, ); - let file_system_sandbox_policy = self.file_system_sandbox_policy(); (file_system_sandbox_policy != legacy_file_system_sandbox_policy) .then_some(file_system_sandbox_policy) } @@ -335,6 +329,14 @@ impl TurnContext { pub(crate) fn to_turn_context_item(&self) -> TurnContextItem { let workspace_roots = self.config.effective_workspace_roots(); + let file_system_sandbox_policy = self.file_system_sandbox_policy(); + let network_sandbox_policy = self.network_sandbox_policy(); + let sandbox_policy = self.permission_profile.compatibility_sandbox_policy( + &file_system_sandbox_policy, + network_sandbox_policy, + #[allow(deprecated)] + &self.cwd, + ); TurnContextItem { turn_id: Some(self.sub_id.clone()), #[allow(deprecated)] @@ -343,7 +345,7 @@ impl TurnContext { current_date: self.current_date.clone(), timezone: self.timezone.clone(), approval_policy: self.approval_policy.value(), - sandbox_policy: self.sandbox_policy(), + sandbox_policy, permission_profile: Some(self.permission_profile()), network: self.turn_context_network_item(), file_system_sandbox_policy: self.non_legacy_file_system_sandbox_policy(), diff --git a/codex-rs/core/src/spawn.rs b/codex-rs/core/src/spawn.rs index a2c4ebe597..a23a1d749e 100644 --- a/codex-rs/core/src/spawn.rs +++ b/codex-rs/core/src/spawn.rs @@ -30,7 +30,7 @@ pub enum StdioPolicy { Inherit, } -/// Spawns the appropriate child process for the ExecParams and SandboxPolicy, +/// Spawns the appropriate child process for the exec params and sandbox settings, /// ensuring the args and environment variables used to create the `Command` /// (and `Child`) honor the configuration. /// 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 9fed922581..6ef90a6bf8 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs @@ -65,6 +65,59 @@ use tokio::sync::Mutex; use tokio::time::timeout; use tokio_util::sync::CancellationToken; +trait LegacyPermissionsForTests { + fn legacy_sandbox_policy( + &self, + cwd: &std::path::Path, + ) -> codex_protocol::protocol::SandboxPolicy; + + fn can_set_legacy_sandbox_policy( + &self, + sandbox_policy: &codex_protocol::protocol::SandboxPolicy, + cwd: &std::path::Path, + ) -> crate::config::ConstraintResult<()>; +} + +impl LegacyPermissionsForTests for crate::config::Permissions { + fn legacy_sandbox_policy( + &self, + cwd: &std::path::Path, + ) -> codex_protocol::protocol::SandboxPolicy { + let permission_profile = self.effective_permission_profile(); + let file_system_policy = permission_profile.file_system_sandbox_policy(); + permission_profile.compatibility_sandbox_policy( + &file_system_policy, + permission_profile.network_sandbox_policy(), + cwd, + ) + } + + fn can_set_legacy_sandbox_policy( + &self, + sandbox_policy: &codex_protocol::protocol::SandboxPolicy, + cwd: &std::path::Path, + ) -> crate::config::ConstraintResult<()> { + let file_system_sandbox_policy = + FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(sandbox_policy, cwd); + let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement( + SandboxEnforcement::from_legacy_sandbox_policy(sandbox_policy), + &file_system_sandbox_policy, + NetworkSandboxPolicy::from(sandbox_policy), + ); + self.can_set_permission_profile(&permission_profile) + } +} + +trait LegacyConfigForTests { + fn legacy_sandbox_policy(&self) -> codex_protocol::protocol::SandboxPolicy; +} + +impl LegacyConfigForTests for crate::config::Config { + fn legacy_sandbox_policy(&self) -> codex_protocol::protocol::SandboxPolicy { + self.permissions.legacy_sandbox_policy(self.cwd.as_path()) + } +} + fn invocation( session: Arc, turn: Arc, @@ -2277,7 +2330,14 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() { .expect("spawned agent thread should exist") .config_snapshot() .await; - assert_eq!(snapshot.sandbox_policy(), expected_sandbox); + let snapshot_file_system_sandbox_policy = + snapshot.permission_profile.file_system_sandbox_policy(); + let snapshot_sandbox_policy = snapshot.permission_profile.compatibility_sandbox_policy( + &snapshot_file_system_sandbox_policy, + snapshot.permission_profile.network_sandbox_policy(), + snapshot.cwd.as_path(), + ); + assert_eq!(snapshot_sandbox_policy, expected_sandbox); assert_eq!(snapshot.approval_policy, AskForApproval::OnRequest); assert_eq!(snapshot.permission_profile, expected_permission_profile); let child_thread = manager diff --git a/codex-rs/core/tests/common/lib.rs b/codex-rs/core/tests/common/lib.rs index 3d7fd21228..1e37966b1c 100644 --- a/codex-rs/core/tests/common/lib.rs +++ b/codex-rs/core/tests/common/lib.rs @@ -16,10 +16,17 @@ use codex_core::CodexThread; use codex_core::config::Config; use codex_core::config::ConfigBuilder; use codex_core::config::ConfigOverrides; +use codex_core::config::ConstraintResult; +use codex_core::config::Permissions; +use codex_protocol::models::PermissionProfile; +use codex_protocol::permissions::FileSystemSandboxPolicy; +use codex_protocol::permissions::NetworkSandboxPolicy; +use codex_protocol::protocol::SandboxPolicy; use codex_utils_absolute_path::AbsolutePathBuf; pub use codex_utils_absolute_path::test_support::PathBufExt; pub use codex_utils_absolute_path::test_support::PathExt; use regex_lite::Regex; +use std::path::Path; use std::path::PathBuf; pub mod apps_test_server; @@ -111,6 +118,55 @@ pub fn test_absolute_path(unix_path: &str) -> AbsolutePathBuf { test_absolute_path_with_windows(unix_path, /*windows_path*/ None) } +pub fn legacy_sandbox_policy_for_config(config: &Config) -> SandboxPolicy { + legacy_sandbox_policy_for_permission_profile( + &config.permissions.effective_permission_profile(), + &config.permissions.file_system_sandbox_policy(), + config.permissions.network_sandbox_policy(), + config.cwd.as_path(), + ) +} + +pub fn legacy_sandbox_policy_for_permission_profile( + permission_profile: &PermissionProfile, + file_system_sandbox_policy: &FileSystemSandboxPolicy, + network_sandbox_policy: NetworkSandboxPolicy, + cwd: &Path, +) -> SandboxPolicy { + permission_profile + .to_legacy_sandbox_policy(cwd) + .unwrap_or_else(|_| { + permission_profile.compatibility_sandbox_policy( + file_system_sandbox_policy, + network_sandbox_policy, + cwd, + ) + }) +} + +pub fn set_legacy_sandbox_policy_for_config( + config: &mut Config, + sandbox_policy: SandboxPolicy, +) -> ConstraintResult<()> { + let cwd = config.cwd.clone(); + set_legacy_sandbox_policy_for_permissions( + &mut config.permissions, + sandbox_policy, + cwd.as_path(), + ) +} + +pub fn set_legacy_sandbox_policy_for_permissions( + permissions: &mut Permissions, + sandbox_policy: SandboxPolicy, + cwd: &Path, +) -> ConstraintResult<()> { + permissions.set_permission_profile(PermissionProfile::from_legacy_sandbox_policy_for_cwd( + &sandbox_policy, + cwd, + )) +} + pub trait TempDirExt { fn abs(&self) -> AbsolutePathBuf; } diff --git a/codex-rs/core/tests/suite/agent_websocket.rs b/codex-rs/core/tests/suite/agent_websocket.rs index c405e35995..1a807b751d 100644 --- a/codex-rs/core/tests/suite/agent_websocket.rs +++ b/codex-rs/core/tests/suite/agent_websocket.rs @@ -1,6 +1,7 @@ use anyhow::Result; use codex_features::Feature; use codex_protocol::config_types::ServiceTier; +use core_test_support::legacy_sandbox_policy_for_config; use core_test_support::responses::WebSocketConnectionConfig; use core_test_support::responses::ev_assistant_message; use core_test_support::responses::ev_completed; @@ -38,8 +39,11 @@ 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.legacy_sandbox_policy()) - .await?; + test.submit_turn_with_policy( + "run the echo command", + legacy_sandbox_policy_for_config(&test.config), + ) + .await?; let connection = server.single_connection(); assert_eq!(connection.len(), 2); @@ -82,7 +86,7 @@ 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.legacy_sandbox_policy()) + test.submit_turn_with_policy("hello", legacy_sandbox_policy_for_config(&test.config)) .await?; assert_eq!(server.handshakes().len(), 1); @@ -145,7 +149,7 @@ 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.legacy_sandbox_policy()) + test.submit_turn_with_policy("hello", legacy_sandbox_policy_for_config(&test.config)) .await?; assert_eq!(server.handshakes().len(), 1); @@ -198,8 +202,11 @@ 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.legacy_sandbox_policy()) - .await?; + test.submit_turn_with_policy( + "run the echo command", + legacy_sandbox_policy_for_config(&test.config), + ) + .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 a91196a0b9..c7233c6185 100644 --- a/codex-rs/core/tests/suite/approvals.rs +++ b/codex-rs/core/tests/suite/approvals.rs @@ -37,6 +37,8 @@ use core_test_support::responses::mount_sse_once; use core_test_support::responses::mount_sse_once_match; use core_test_support::responses::sse; use core_test_support::responses::start_mock_server; +use core_test_support::set_legacy_sandbox_policy_for_config; +use core_test_support::set_legacy_sandbox_policy_for_permissions; use core_test_support::skip_if_no_network; use core_test_support::test_codex::TestCodex; use core_test_support::test_codex::test_codex; @@ -1898,8 +1900,7 @@ 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 - .set_legacy_sandbox_policy(sandbox_policy.clone()) + set_legacy_sandbox_policy_for_config(config, sandbox_policy.clone()) .expect("set sandbox policy"); for feature in features { config @@ -2073,8 +2074,7 @@ 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 - .set_legacy_sandbox_policy(sandbox_policy_for_config) + set_legacy_sandbox_policy_for_config(config, sandbox_policy_for_config) .expect("set sandbox policy"); config.approvals_reviewer = ApprovalsReviewer::User; }); @@ -2184,8 +2184,7 @@ 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 - .set_legacy_sandbox_policy(sandbox_policy_for_config) + set_legacy_sandbox_policy_for_config(config, sandbox_policy_for_config) .expect("set sandbox policy"); }); let test = builder.build(&server).await?; @@ -2357,8 +2356,7 @@ 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 - .set_legacy_sandbox_policy(sandbox_policy_for_config) + set_legacy_sandbox_policy_for_config(config, sandbox_policy_for_config) .expect("set sandbox policy"); config .features @@ -2809,8 +2807,7 @@ 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 - .set_legacy_sandbox_policy(sandbox_policy_for_config) + set_legacy_sandbox_policy_for_config(config, sandbox_policy_for_config) .expect("set sandbox policy"); }); let test = builder.build(&server).await?; @@ -2862,8 +2859,7 @@ 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 - .set_legacy_sandbox_policy(sandbox_policy_for_config) + set_legacy_sandbox_policy_for_config(config, sandbox_policy_for_config) .expect("set sandbox policy"); }); let test = builder.build(&server).await?; @@ -3002,8 +2998,7 @@ allow_local_binding = true .with_cloud_requirements(managed_network_requirements_loader()) .with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config - .set_legacy_sandbox_policy(sandbox_policy_for_config) + set_legacy_sandbox_policy_for_config(config, sandbox_policy_for_config) .expect("set sandbox policy"); }); let test = builder.build(&server).await?; @@ -3488,10 +3483,12 @@ allow_local_binding = true .with_config(move |config| { config.permissions.approval_policy = Constrained::allow_any(approval_policy); let cwd = config.cwd.clone(); - config - .permissions - .set_legacy_sandbox_policy(SandboxPolicy::DangerFullAccess, cwd.as_path()) - .expect("test setup should allow sandbox policy"); + set_legacy_sandbox_policy_for_permissions( + &mut config.permissions, + SandboxPolicy::DangerFullAccess, + cwd.as_path(), + ) + .expect("test setup should allow sandbox policy"); }); let test = builder.build(&server).await?; assert!( @@ -3611,8 +3608,7 @@ 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 - .set_legacy_sandbox_policy(sandbox_policy_for_config) + set_legacy_sandbox_policy_for_config(config, 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 7234c59c0f..c77bd4daad 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -50,6 +50,7 @@ use codex_protocol::protocol::SessionSource; use codex_protocol::user_input::UserInput; use core_test_support::PathBufExt; use core_test_support::apps_test_server::AppsTestServer; +use core_test_support::legacy_sandbox_policy_for_config; use core_test_support::load_default_config_for_test; use core_test_support::responses::ResponsesRequest; use core_test_support::responses::ev_assistant_message; @@ -1797,7 +1798,7 @@ async fn user_turn_collaboration_mode_overrides_model_and_effort() -> anyhow::Re thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(config.cwd.to_path_buf()), approval_policy: Some(config.permissions.approval_policy.value()), - sandbox_policy: Some(config.legacy_sandbox_policy()), + sandbox_policy: Some(legacy_sandbox_policy_for_config(&config)), summary: Some( config .model_reasoning_summary @@ -1920,7 +1921,7 @@ async fn user_turn_explicit_reasoning_summary_overrides_model_catalog_default() thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(config.cwd.to_path_buf()), approval_policy: Some(config.permissions.approval_policy.value()), - sandbox_policy: Some(config.legacy_sandbox_policy()), + sandbox_policy: Some(legacy_sandbox_policy_for_config(&config)), summary: Some(ReasoningSummary::Concise), collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { mode: codex_protocol::config_types::ModeKind::Default, diff --git a/codex-rs/core/tests/suite/collaboration_instructions.rs b/codex-rs/core/tests/suite/collaboration_instructions.rs index bbbb0f1aa2..26e99bf5a5 100644 --- a/codex-rs/core/tests/suite/collaboration_instructions.rs +++ b/codex-rs/core/tests/suite/collaboration_instructions.rs @@ -7,6 +7,7 @@ use codex_protocol::protocol::COLLABORATION_MODE_OPEN_TAG; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::Op; use codex_protocol::user_input::UserInput; +use core_test_support::legacy_sandbox_policy_for_config; use core_test_support::responses::ev_completed; use core_test_support::responses::ev_response_created; use core_test_support::responses::mount_sse_once; @@ -176,7 +177,7 @@ async fn collaboration_instructions_added_on_user_turn() -> Result<()> { thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.config.cwd.to_path_buf()), approval_policy: Some(test.config.permissions.approval_policy.value()), - sandbox_policy: Some(test.config.legacy_sandbox_policy()), + sandbox_policy: Some(legacy_sandbox_policy_for_config(&test.config)), summary: Some( test.config .model_reasoning_summary @@ -227,7 +228,7 @@ async fn collaboration_instructions_omitted_when_disabled() -> Result<()> { thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.config.cwd.to_path_buf()), approval_policy: Some(test.config.permissions.approval_policy.value()), - sandbox_policy: Some(test.config.legacy_sandbox_policy()), + sandbox_policy: Some(legacy_sandbox_policy_for_config(&test.config)), summary: Some( test.config .model_reasoning_summary @@ -336,7 +337,7 @@ async fn user_turn_overrides_collaboration_instructions_after_override() -> Resu thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.config.cwd.to_path_buf()), approval_policy: Some(test.config.permissions.approval_policy.value()), - sandbox_policy: Some(test.config.legacy_sandbox_policy()), + sandbox_policy: Some(legacy_sandbox_policy_for_config(&test.config)), summary: Some( test.config .model_reasoning_summary diff --git a/codex-rs/core/tests/suite/guardian_review.rs b/codex-rs/core/tests/suite/guardian_review.rs index df38fb4353..3fde1bcf42 100644 --- a/codex-rs/core/tests/suite/guardian_review.rs +++ b/codex-rs/core/tests/suite/guardian_review.rs @@ -17,6 +17,7 @@ use core_test_support::responses::ev_response_created; use core_test_support::responses::mount_sse_sequence; use core_test_support::responses::sse; use core_test_support::responses::start_mock_server; +use core_test_support::set_legacy_sandbox_policy_for_config; use core_test_support::skip_if_no_network; use core_test_support::skip_if_sandbox; use core_test_support::test_codex::test_codex; @@ -60,8 +61,7 @@ printf '%s\n' "${@: -1}" >> "${payload_path}""#, let mut builder = test_codex().with_config(move |config| { config.notify = Some(vec![notify_script_str]); config.permissions.approval_policy = Constrained::allow_any(approval_policy); - config - .set_legacy_sandbox_policy(sandbox_policy_for_config) + set_legacy_sandbox_policy_for_config(config, sandbox_policy_for_config) .expect("set sandbox policy"); }); let test = builder.build(&server).await?; diff --git a/codex-rs/core/tests/suite/prompt_caching.rs b/codex-rs/core/tests/suite/prompt_caching.rs index 343a49ec7f..592aebe0a6 100644 --- a/codex-rs/core/tests/suite/prompt_caching.rs +++ b/codex-rs/core/tests/suite/prompt_caching.rs @@ -17,6 +17,7 @@ use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::Op; use codex_protocol::user_input::UserInput; use core_test_support::TempDirExt; +use core_test_support::legacy_sandbox_policy_for_config; use core_test_support::responses::ev_completed; use core_test_support::responses::ev_response_created; use core_test_support::responses::mount_sse_once; @@ -855,7 +856,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.legacy_sandbox_policy(); + let default_sandbox_policy = &legacy_sandbox_policy_for_config(&config); let default_model = session_configured.model; let default_effort = config.model_reasoning_effort; let default_summary = config.model_reasoning_summary; @@ -996,7 +997,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.legacy_sandbox_policy(); + let default_sandbox_policy = &legacy_sandbox_policy_for_config(&config); 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/resume_warning.rs b/codex-rs/core/tests/suite/resume_warning.rs index 9cb2980725..15b2b5782b 100644 --- a/codex-rs/core/tests/suite/resume_warning.rs +++ b/codex-rs/core/tests/suite/resume_warning.rs @@ -15,6 +15,7 @@ use codex_protocol::protocol::TurnStartedEvent; use codex_protocol::protocol::UserMessageEvent; use codex_protocol::protocol::WarningEvent; use core::time::Duration; +use core_test_support::legacy_sandbox_policy_for_config; use core_test_support::load_default_config_for_test; use core_test_support::wait_for_event; use tempfile::TempDir; @@ -32,7 +33,7 @@ fn resume_history( current_date: None, timezone: None, approval_policy: config.permissions.approval_policy.value(), - sandbox_policy: config.legacy_sandbox_policy(), + sandbox_policy: legacy_sandbox_policy_for_config(config), permission_profile: None, network: None, file_system_sandbox_policy: None, diff --git a/codex-rs/memories/write/src/phase2.rs b/codex-rs/memories/write/src/phase2.rs index c78032d9c2..30440a0ff4 100644 --- a/codex-rs/memories/write/src/phase2.rs +++ b/codex-rs/memories/write/src/phase2.rs @@ -17,9 +17,10 @@ use codex_config::Constrained; use codex_core::config::Config; use codex_features::Feature; use codex_protocol::ThreadId; +use codex_protocol::models::PermissionProfile; +use codex_protocol::permissions::NetworkSandboxPolicy; use codex_protocol::protocol::AgentStatus; use codex_protocol::protocol::AskForApproval; -use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::TokenUsage; use codex_protocol::user_input::UserInput; use codex_state::Stage1Output; @@ -320,18 +321,18 @@ mod agent { .features .disable(Feature::SkillMcpDependencyInstall); - // Sandbox policy - let writable_roots = vec![root]; // The consolidation agent only needs local memory-root write access and no network. - let consolidation_sandbox_policy = SandboxPolicy::WorkspaceWrite { - writable_roots, - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; + agent_config.workspace_roots = vec![root.clone()]; + agent_config.workspace_roots_explicit = true; + agent_config.permissions.set_workspace_roots(vec![root]); agent_config .permissions - .set_legacy_sandbox_policy(consolidation_sandbox_policy, agent_config.cwd.as_path()) + .set_permission_profile(PermissionProfile::workspace_write_with( + &[], + NetworkSandboxPolicy::Restricted, + /*exclude_tmpdir_env_var*/ true, + /*exclude_slash_tmp*/ true, + )) .ok()?; agent_config.model = Some( @@ -574,3 +575,25 @@ fn emit_token_usage_metrics(context: &MemoryStartupContext, token_usage: &TokenU &[("token_type", "reasoning_output")], ); } + +#[cfg(test)] +mod tests { + use super::*; + use core_test_support::load_default_config_for_test; + use pretty_assertions::assert_eq; + use tempfile::TempDir; + + #[tokio::test] + async fn consolidation_agent_config_uses_memory_root_as_workspace_root() { + let home = TempDir::new().expect("temp dir"); + let config = load_default_config_for_test(&home).await; + let root = memory_root(&config.codex_home); + + let agent_config = agent::get_config(&config).expect("agent config"); + + assert_eq!(agent_config.cwd, root); + assert_eq!(agent_config.workspace_roots, vec![root.clone()]); + assert_eq!(agent_config.permissions.workspace_roots(), &[root]); + assert!(agent_config.workspace_roots_explicit); + } +} diff --git a/codex-rs/otel/src/events/session_telemetry.rs b/codex-rs/otel/src/events/session_telemetry.rs index 1da6497eb0..030b766f0e 100644 --- a/codex-rs/otel/src/events/session_telemetry.rs +++ b/codex-rs/otel/src/events/session_telemetry.rs @@ -40,7 +40,6 @@ use codex_protocol::models::ResponseItem; use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::ReviewDecision; -use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::SessionSource; use codex_protocol::user_input::UserInput; use eventsource_stream::Event as StreamEvent; @@ -444,7 +443,7 @@ impl SessionTelemetry { context_window: Option, auto_compact_token_limit: Option, approval_policy: AskForApproval, - sandbox_policy: SandboxPolicy, + sandbox_policy: impl std::fmt::Display, mcp_servers: Vec<&str>, ) { log_and_trace_event!( diff --git a/codex-rs/protocol/src/models.rs b/codex-rs/protocol/src/models.rs index cbf8895ddb..15a6a960d7 100644 --- a/codex-rs/protocol/src/models.rs +++ b/codex-rs/protocol/src/models.rs @@ -544,6 +544,42 @@ impl PermissionProfile { } } + pub fn compatibility_sandbox_policy( + &self, + file_system_policy: &FileSystemSandboxPolicy, + network_policy: NetworkSandboxPolicy, + cwd: &Path, + ) -> SandboxPolicy { + self.to_legacy_sandbox_policy(cwd).unwrap_or_else(|_| { + let cwd_abs = AbsolutePathBuf::from_absolute_path(cwd).ok(); + let writable_roots = file_system_policy + .get_writable_roots_with_cwd(cwd) + .into_iter() + .map(|root| root.root) + .filter(|root| cwd_abs.as_ref() != Some(root)) + .collect(); + let tmpdir_writable = std::env::var_os("TMPDIR") + .filter(|tmpdir| !tmpdir.is_empty()) + .and_then(|tmpdir| { + AbsolutePathBuf::from_absolute_path(std::path::PathBuf::from(tmpdir)).ok() + }) + .is_some_and(|tmpdir| { + file_system_policy.can_write_path_with_cwd(tmpdir.as_path(), cwd) + }); + let slash_tmp = Path::new("/tmp"); + let slash_tmp_writable = slash_tmp.is_absolute() + && slash_tmp.is_dir() + && file_system_policy.can_write_path_with_cwd(slash_tmp, cwd); + + SandboxPolicy::WorkspaceWrite { + writable_roots, + network_access: network_policy.is_enabled(), + exclude_tmpdir_env_var: !tmpdir_writable, + exclude_slash_tmp: !slash_tmp_writable, + } + }) + } + pub fn to_runtime_permissions(&self) -> (FileSystemSandboxPolicy, NetworkSandboxPolicy) { ( self.file_system_sandbox_policy(), diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index c83c492e72..74c83835ef 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -475,6 +475,37 @@ pub struct ThreadSettingsOverrides { pub personality: Option, } +impl ThreadSettingsOverrides { + pub fn permission_profile_update_for_cwd( + &self, + cwd: &Path, + current_file_system_sandbox_policy: Option<&FileSystemSandboxPolicy>, + ) -> Option { + if let Some(permission_profile) = self.permission_profile.clone() { + return Some(permission_profile); + } + + self.sandbox_policy.as_ref().map(|sandbox_policy| { + let file_system_sandbox_policy = if let Some(current_file_system_sandbox_policy) = + current_file_system_sandbox_policy + { + FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries( + sandbox_policy, + cwd, + current_file_system_sandbox_policy, + ) + } else { + FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(sandbox_policy, cwd) + }; + PermissionProfile::from_runtime_permissions_with_enforcement( + SandboxEnforcement::from_legacy_sandbox_policy(sandbox_policy), + &file_system_sandbox_policy, + NetworkSandboxPolicy::from(sandbox_policy), + ) + }) + } +} + /// Source classification for client-supplied context. #[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] diff --git a/codex-rs/sandboxing/src/manager.rs b/codex-rs/sandboxing/src/manager.rs index 82c49f7908..440ebc4de7 100644 --- a/codex-rs/sandboxing/src/manager.rs +++ b/codex-rs/sandboxing/src/manager.rs @@ -266,42 +266,7 @@ pub fn compatibility_sandbox_policy_for_permission_profile( network_policy: NetworkSandboxPolicy, cwd: &Path, ) -> SandboxPolicy { - permissions - .to_legacy_sandbox_policy(cwd) - .unwrap_or_else(|_| { - compatibility_workspace_write_policy(file_system_policy, network_policy, cwd) - }) -} - -fn compatibility_workspace_write_policy( - file_system_policy: &FileSystemSandboxPolicy, - network_policy: NetworkSandboxPolicy, - cwd: &Path, -) -> SandboxPolicy { - let cwd_abs = AbsolutePathBuf::from_absolute_path(cwd).ok(); - let writable_roots = file_system_policy - .get_writable_roots_with_cwd(cwd) - .into_iter() - .map(|root| root.root) - .filter(|root| cwd_abs.as_ref() != Some(root)) - .collect(); - let tmpdir_writable = std::env::var_os("TMPDIR") - .filter(|tmpdir| !tmpdir.is_empty()) - .and_then(|tmpdir| { - AbsolutePathBuf::from_absolute_path(std::path::PathBuf::from(tmpdir)).ok() - }) - .is_some_and(|tmpdir| file_system_policy.can_write_path_with_cwd(tmpdir.as_path(), cwd)); - let slash_tmp = Path::new("/tmp"); - let slash_tmp_writable = slash_tmp.is_absolute() - && slash_tmp.is_dir() - && file_system_policy.can_write_path_with_cwd(slash_tmp, cwd); - - SandboxPolicy::WorkspaceWrite { - writable_roots, - network_access: network_policy.is_enabled(), - exclude_tmpdir_env_var: !tmpdir_writable, - exclude_slash_tmp: !slash_tmp_writable, - } + permissions.compatibility_sandbox_policy(file_system_policy, network_policy, cwd) } #[cfg(target_os = "linux")] diff --git a/codex-rs/utils/sandbox-summary/src/config_summary.rs b/codex-rs/utils/sandbox-summary/src/config_summary.rs index b3de5b6382..f008f23299 100644 --- a/codex-rs/utils/sandbox-summary/src/config_summary.rs +++ b/codex-rs/utils/sandbox-summary/src/config_summary.rs @@ -1,7 +1,7 @@ use codex_core::config::Config; use codex_model_provider_info::WireApi; -use crate::sandbox_summary::summarize_sandbox_policy; +use crate::sandbox_summary::summarize_permission_profile; /// Build a list of key/value pairs summarizing the effective configuration. pub fn create_config_summary_entries(config: &Config, model: &str) -> Vec<(&'static str, String)> { @@ -15,10 +15,10 @@ pub fn create_config_summary_entries(config: &Config, model: &str) -> Vec<(&'sta ), ( "sandbox", - summarize_sandbox_policy( - &config - .permissions - .legacy_sandbox_policy(config.cwd.as_path()), + summarize_permission_profile( + &config.permissions.effective_permission_profile(), + &config.cwd, + &config.effective_workspace_roots(), ), ), ];