From 8444cf63b50a8a88521e0d2970d49f659b48eac7 Mon Sep 17 00:00:00 2001 From: "Adam Perry @ OpenAI" Date: Tue, 25 Aug 2026 05:05:35 +0000 Subject: [PATCH] Derive sandbox contexts from turn environments (#40551) ## What changed - Add Windows sandbox mode, private-desktop behavior, and legacy Landlock selection to each `EnvironmentConfig`. - Centralize sandbox context construction on `TurnEnvironment`, preserving environment-owned permissions, paths, and backend settings while applying additional permission grants. - Use the environment-derived context for project instructions, capability discovery, file uploads, and filesystem-oriented tools. ## Testing - Verify capability discovery uses the selected environment's permission profile and sandbox backends when they differ from the thread defaults. GitOrigin-RevId: 7ae2fed90c8ada950f2e8431e571e9b2c793c291 --- codex-rs/codex-mcp/src/mcp/mod.rs | 2 + codex-rs/core/src/agents_md.rs | 18 +----- codex-rs/core/src/agents_md_manager.rs | 18 ++---- codex-rs/core/src/agents_md_tests.rs | 58 ++++++------------- codex-rs/core/src/apply_patch.rs | 2 + codex-rs/core/src/environment_selection.rs | 16 +++++ codex-rs/core/src/exec.rs | 2 + codex-rs/core/src/exec_policy.rs | 4 ++ codex-rs/core/src/mcp_openai_file.rs | 5 +- codex-rs/core/src/mcp_tool_call.rs | 2 + codex-rs/core/src/mcp_tool_call_tests.rs | 6 ++ codex-rs/core/src/sandboxing/mod.rs | 2 + codex-rs/core/src/session/mcp.rs | 25 ++------ codex-rs/core/src/session/mcp_runtime.rs | 6 +- codex-rs/core/src/session/mod.rs | 14 ++--- codex-rs/core/src/session/session.rs | 16 +++-- codex-rs/core/src/session/tests.rs | 48 +++++++++++++-- codex-rs/core/src/session/turn_context.rs | 54 ++++++++--------- codex-rs/core/src/thread_manager_tests.rs | 5 ++ .../core/src/tools/handlers/apply_patch.rs | 6 +- .../src/tools/handlers/extension_tools.rs | 4 +- .../handlers/unified_exec/exec_command.rs | 2 + .../core/src/tools/handlers/view_image.rs | 3 +- codex-rs/core/src/tools/orchestrator.rs | 2 + codex-rs/core/src/tools/registry.rs | 2 + .../src/tools/runtimes/apply_patch_tests.rs | 3 + .../core/src/tools/runtimes/unified_exec.rs | 4 ++ .../runtimes/zsh_fork/unix_escalation.rs | 4 ++ codex-rs/core/src/tools/sandboxing.rs | 2 + codex-rs/core/src/tools/spec_plan_tests.rs | 6 ++ codex-rs/core/src/turn_metadata.rs | 2 + codex-rs/core/tests/suite/agent_execution.rs | 8 +++ codex-rs/core/tests/suite/exec_policy.rs | 20 +++++++ codex-rs/core/tests/suite/guardian_review.rs | 5 ++ codex-rs/core/tests/suite/hooks_executor.rs | 9 +++ .../suite/mcp_startup_refresh_http_proxy.rs | 5 ++ .../core/tests/suite/mcp_tool_exposure.rs | 8 +++ codex-rs/core/tests/suite/network_approval.rs | 8 +++ codex-rs/core/tests/suite/remote_env.rs | 44 ++++++++++++++ codex-rs/core/tests/suite/rmcp_client.rs | 8 +++ codex-rs/core/tests/suite/unified_exec.rs | 9 +++ codex-rs/protocol/src/environment.rs | 13 +++++ codex-rs/sandboxing/src/manager.rs | 4 ++ codex-rs/sandboxing/src/spawn.rs | 2 + codex-rs/tui/src/app/thread_settings.rs | 2 + .../windows-sandbox-rs/src/elevated_impl.rs | 2 + .../src/unified_exec/mod.rs | 2 + 47 files changed, 341 insertions(+), 151 deletions(-) diff --git a/codex-rs/codex-mcp/src/mcp/mod.rs b/codex-rs/codex-mcp/src/mcp/mod.rs index 05dae0c570..564d690ecf 100644 --- a/codex-rs/codex-mcp/src/mcp/mod.rs +++ b/codex-rs/codex-mcp/src/mcp/mod.rs @@ -147,6 +147,8 @@ pub struct McpConfig { /// Optional path to `codex-linux-sandbox` for sandboxed MCP tool execution. pub codex_linux_sandbox_exe: Option, /// Whether to use legacy Landlock behavior in the MCP sandbox state. + // TODO(anp): Reconcile this runtime-wide copy with TurnEnvironment::sandbox_context + // for the environment that owns each MCP server. pub use_legacy_landlock: bool, /// Whether the app MCP integration is enabled by config. /// diff --git a/codex-rs/core/src/agents_md.rs b/codex-rs/core/src/agents_md.rs index 1cbbc5bfcf..1d9fc4947c 100644 --- a/codex-rs/core/src/agents_md.rs +++ b/codex-rs/core/src/agents_md.rs @@ -18,7 +18,6 @@ use crate::config::Config; use crate::context::UserInstructions as ContextUserInstructions; use crate::environment_selection::TurnEnvironmentSnapshot; -use crate::tools::sandboxing::executor_windows_sandbox_level; use codex_config::ConfigLayerSource; use codex_config::default_project_root_markers; use codex_config::merge_toml_values; @@ -30,7 +29,6 @@ use codex_extension_api::UserInstructions; use codex_file_system::FileSystemSandboxContext; use codex_file_system::FindUpErrorPolicy; use codex_file_system::find_nearest_ancestor_with_markers; -use codex_protocol::config_types::WindowsSandboxLevel; use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_path_uri::PathUri; use futures::StreamExt; @@ -58,7 +56,6 @@ pub(crate) async fn load_project_instructions( config: &Config, user_instructions: Option, environments: &TurnEnvironmentSnapshot, - windows_sandbox_level: WindowsSandboxLevel, ) -> io::Result> { let mut loaded = LoadedAgentsMd::from_user_instructions(user_instructions); if config.active_project.is_untrusted() { @@ -76,20 +73,7 @@ pub(crate) async fn load_project_instructions( .permission_profile() .file_system_sandbox_policy() .has_full_disk_read_access()) - .then(|| { - // TODO(anp): Move sandbox context construction to a method on TurnEnvironment. - let mut sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd( - turn_environment.permission_profile().clone(), - turn_environment.cwd().clone(), - ); - sandbox.workspace_roots = turn_environment.workspace_roots().to_vec(); - sandbox.windows_sandbox_level = - executor_windows_sandbox_level(windows_sandbox_level, turn_environment.cwd()); - sandbox.windows_sandbox_private_desktop = - config.permissions.windows_sandbox_private_desktop; - sandbox.use_legacy_landlock = config.features.use_legacy_landlock(); - sandbox - }); + .then(|| turn_environment.sandbox_context(/*additional_permissions*/ None)); match read_agents_md( config, filesystem.as_ref(), diff --git a/codex-rs/core/src/agents_md_manager.rs b/codex-rs/core/src/agents_md_manager.rs index 7117fd8f6d..6982440f13 100644 --- a/codex-rs/core/src/agents_md_manager.rs +++ b/codex-rs/core/src/agents_md_manager.rs @@ -4,7 +4,6 @@ use crate::config::Config; use crate::environment_selection::TurnEnvironmentSnapshot; use codex_extension_api::UserInstructions; use codex_protocol::config_types::TrustLevel; -use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::protocol::TurnEnvironmentSelection; use std::io; use std::sync::Arc; @@ -20,7 +19,6 @@ pub(crate) struct AgentsMdManager { struct AgentsMdCache { selections: Option>, active_project_trust_level: Option, - windows_sandbox_level: Option, loaded: Option>, } @@ -38,7 +36,6 @@ impl AgentsMdManager { &self, config: &Config, environments: &TurnEnvironmentSnapshot, - windows_sandbox_level: WindowsSandboxLevel, ) -> io::Result<()> { let selections = environments .turn_environments() @@ -49,28 +46,21 @@ impl AgentsMdManager { let mut cache = self.cache.lock().await; if cache.selections.as_ref() == Some(&selections) && cache.active_project_trust_level == active_project_trust_level - && cache.windows_sandbox_level == Some(windows_sandbox_level) { return Ok(()); } cache.selections = None; cache.active_project_trust_level = None; - cache.windows_sandbox_level = None; cache.loaded = None; } - let loaded = load_project_instructions( - config, - self.user_instructions.clone(), - environments, - windows_sandbox_level, - ) - .await? - .map(Arc::new); + let loaded = + load_project_instructions(config, self.user_instructions.clone(), environments) + .await? + .map(Arc::new); let mut cache = self.cache.lock().await; cache.selections = Some(selections); cache.active_project_trust_level = active_project_trust_level; - cache.windows_sandbox_level = Some(windows_sandbox_level); cache.loaded = loaded; Ok(()) } diff --git a/codex-rs/core/src/agents_md_tests.rs b/codex-rs/core/src/agents_md_tests.rs index c4c77ff2a5..bdac2e91d7 100644 --- a/codex-rs/core/src/agents_md_tests.rs +++ b/codex-rs/core/src/agents_md_tests.rs @@ -339,7 +339,6 @@ async fn load_agents_md(config: &TestConfig) -> Option { &config.config, config.user_instructions.clone(), &environments, - WindowsSandboxLevel::Disabled, ) .await .expect("project instructions should load") @@ -369,6 +368,9 @@ fn resolved_local_environments( workspace_roots: Vec::new(), config: EnvironmentConfigState::Ready(EnvironmentConfig { allow_login_shell: true, + windows_sandbox_level: WindowsSandboxLevel::Disabled, + windows_sandbox_private_desktop: true, + use_legacy_landlock: false, permission_profile: PermissionProfileSnapshot::legacy( PermissionProfile::read_only(), ), @@ -1133,15 +1135,10 @@ async fn multiple_environment_docs_use_labeled_layout_and_preserve_source_order( ]); let user_instructions = config.user_instructions.clone(); - let loaded = load_project_instructions( - &config.config, - user_instructions, - &environments, - WindowsSandboxLevel::Disabled, - ) - .await - .expect("project instructions should load") - .expect("instructions expected"); + let loaded = load_project_instructions(&config.config, user_instructions, &environments) + .await + .expect("project instructions should load") + .expect("instructions expected"); let inner = format!( r#"global instructions @@ -1200,15 +1197,10 @@ async fn secondary_only_project_doc_uses_single_contributor_layout() { ]); let user_instructions = config.user_instructions.clone(); - let loaded = load_project_instructions( - &config.config, - user_instructions, - &environments, - WindowsSandboxLevel::Disabled, - ) - .await - .expect("project instructions should load") - .expect("instructions expected"); + let loaded = load_project_instructions(&config.config, user_instructions, &environments) + .await + .expect("project instructions should load") + .expect("instructions expected"); let inner = format!("global instructions{AGENTS_MD_SEPARATOR}secondary doc"); assert_eq!(loaded.legacy_text(), inner); @@ -1235,15 +1227,10 @@ async fn primary_only_project_doc_preserves_legacy_layout_with_multiple_bound_en ]); let user_instructions = config.user_instructions.clone(); - let loaded = load_project_instructions( - &config.config, - user_instructions, - &environments, - WindowsSandboxLevel::Disabled, - ) - .await - .expect("project instructions should load") - .expect("instructions expected"); + let loaded = load_project_instructions(&config.config, user_instructions, &environments) + .await + .expect("project instructions should load") + .expect("instructions expected"); let inner = format!("global instructions{AGENTS_MD_SEPARATOR}primary doc"); assert_eq!(loaded.legacy_text(), inner); @@ -1271,15 +1258,10 @@ async fn project_doc_byte_limit_is_shared_across_environments() { ]); let user_instructions = config.user_instructions.clone(); - let loaded = load_project_instructions( - &config.config, - user_instructions, - &environments, - WindowsSandboxLevel::Disabled, - ) - .await - .expect("project instructions should load") - .expect("instructions expected"); + let loaded = load_project_instructions(&config.config, user_instructions, &environments) + .await + .expect("project instructions should load") + .expect("instructions expected"); assert_eq!( loaded.text(), @@ -1310,7 +1292,6 @@ async fn full_primary_environment_budget_excludes_later_environment_docs() { &config.config, /*user_instructions*/ None, &environments, - WindowsSandboxLevel::Disabled, ) .await .expect("project instructions should load") @@ -1343,7 +1324,6 @@ async fn secondary_environment_invalid_utf8_does_not_suppress_other_docs() { &config.config, /*user_instructions*/ None, &environments, - WindowsSandboxLevel::Disabled, ) .await .expect("project instructions should load") diff --git a/codex-rs/core/src/apply_patch.rs b/codex-rs/core/src/apply_patch.rs index 67b53cb0b6..6075fa17d1 100644 --- a/codex-rs/core/src/apply_patch.rs +++ b/codex-rs/core/src/apply_patch.rs @@ -25,6 +25,8 @@ pub(crate) fn prepare_apply_patch( file_system_sandbox_policy: &FileSystemSandboxPolicy, action: ApplyPatchAction, ) -> Result { + // TODO(anp): Reconcile patch approval with TurnEnvironment::sandbox_context + // so the Windows backend matches the environment that will apply the patch. match assess_patch_safety( &action, turn_context.approval_policy(), diff --git a/codex-rs/core/src/environment_selection.rs b/codex-rs/core/src/environment_selection.rs index 2210f2823c..3d3989eb73 100644 --- a/codex-rs/core/src/environment_selection.rs +++ b/codex-rs/core/src/environment_selection.rs @@ -878,6 +878,7 @@ mod tests { use codex_exec_server_test_support::environment_manager_without_environments; use codex_http_client::HttpClientFactory; use codex_http_client::OutboundProxyPolicy; + use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::ActivePermissionProfile; use codex_protocol::models::PermissionProfile; use codex_protocol::protocol::TurnEnvironmentSelection; @@ -902,6 +903,9 @@ mod tests { fn test_environment_config() -> EnvironmentConfig { EnvironmentConfig { allow_login_shell: true, + windows_sandbox_level: WindowsSandboxLevel::Disabled, + windows_sandbox_private_desktop: true, + use_legacy_landlock: false, permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::read_only()), shell_environment_policy: Default::default(), exec_policy: None, @@ -1073,6 +1077,9 @@ url = "ws://127.0.0.1:8765" }; let expected_config = EnvironmentConfig { allow_login_shell: false, + windows_sandbox_level: WindowsSandboxLevel::Disabled, + windows_sandbox_private_desktop: true, + use_legacy_landlock: false, permission_profile: PermissionProfileSnapshot::active_with_profile_workspace_roots( PermissionProfile::read_only(), ActivePermissionProfile::read_only(), @@ -1309,6 +1316,9 @@ url = "ws://127.0.0.1:8765" let cwd = AbsolutePathBuf::current_dir().expect("cwd"); let expected_config = EnvironmentConfig { allow_login_shell: false, + windows_sandbox_level: WindowsSandboxLevel::Disabled, + windows_sandbox_private_desktop: true, + use_legacy_landlock: false, permission_profile: PermissionProfileSnapshot::active_with_profile_workspace_roots( PermissionProfile::read_only(), ActivePermissionProfile::read_only(), @@ -1646,6 +1656,9 @@ url = "ws://127.0.0.1:8765" .expect("replacement environment"); let child_config = EnvironmentConfig { allow_login_shell: false, + windows_sandbox_level: WindowsSandboxLevel::Disabled, + windows_sandbox_private_desktop: true, + use_legacy_landlock: false, permission_profile: PermissionProfileSnapshot::active_with_profile_workspace_roots( PermissionProfile::read_only(), ActivePermissionProfile::read_only(), @@ -1697,6 +1710,9 @@ url = "ws://127.0.0.1:8765" resolve_turn_environments(Arc::clone(&manager), std::slice::from_ref(&selection)).await; let parent_owner_config = EnvironmentConfig { allow_login_shell: false, + windows_sandbox_level: WindowsSandboxLevel::Disabled, + windows_sandbox_private_desktop: true, + use_legacy_landlock: false, permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::read_only()), shell_environment_policy: Default::default(), exec_policy: None, diff --git a/codex-rs/core/src/exec.rs b/codex-rs/core/src/exec.rs index 8f8a256b31..46c24421be 100644 --- a/codex-rs/core/src/exec.rs +++ b/codex-rs/core/src/exec.rs @@ -101,6 +101,8 @@ pub struct ExecParams { pub network: Option, pub network_environment_id: Option, pub sandbox_permissions: SandboxPermissions, + // TODO(anp): Reconcile these launch settings with TurnEnvironment::sandbox_context + // so turn-scoped execution uses the selected environment's backend. pub windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel, pub windows_sandbox_private_desktop: bool, pub justification: Option, diff --git a/codex-rs/core/src/exec_policy.rs b/codex-rs/core/src/exec_policy.rs index f24b062469..bbbe6a27ca 100644 --- a/codex-rs/core/src/exec_policy.rs +++ b/codex-rs/core/src/exec_policy.rs @@ -167,6 +167,8 @@ pub(crate) enum ExecPolicyCommandOrigin { pub(crate) struct UnmatchedCommandContext<'a> { pub(crate) approval_policy: AskForApproval, pub(crate) permission_profile: &'a PermissionProfile, + // TODO(anp): Reconcile this decision input with TurnEnvironment::sandbox_context + // so approval heuristics match the selected environment's Windows backend. pub(crate) windows_sandbox_level: WindowsSandboxLevel, pub(crate) sandbox_permissions: SandboxPermissions, pub(crate) command_origin: ExecPolicyCommandOrigin, @@ -281,6 +283,8 @@ pub(crate) struct ExecApprovalRequest<'a> { pub(crate) approval_policy: AskForApproval, pub(crate) permission_profile: PermissionProfile, pub(crate) environment_policy: Option<&'a RequirementsExecPolicy>, + // TODO(anp): Reconcile this approval snapshot with TurnEnvironment::sandbox_context + // rather than taking the Windows backend from the turn-wide default. pub(crate) windows_sandbox_level: WindowsSandboxLevel, pub(crate) sandbox_permissions: SandboxPermissions, pub(crate) prefix_rule: Option>, diff --git a/codex-rs/core/src/mcp_openai_file.rs b/codex-rs/core/src/mcp_openai_file.rs index 1320519b03..d135f60a25 100644 --- a/codex-rs/core/src/mcp_openai_file.rs +++ b/codex-rs/core/src/mcp_openai_file.rs @@ -180,9 +180,8 @@ async fn build_uploaded_argument_value( .entries .iter() .any(|entry| entry.access == FileSystemAccessMode::Deny); - let sandbox = requires_sandbox.then(|| { - turn_context.file_system_sandbox_context(additional_permissions, turn_environment) - }); + let sandbox = + requires_sandbox.then(|| turn_environment.sandbox_context(additional_permissions)); if sandbox.is_some() { let environment_info = turn_environment .environment diff --git a/codex-rs/core/src/mcp_tool_call.rs b/codex-rs/core/src/mcp_tool_call.rs index 12e51eeb5b..2bbb831ad7 100644 --- a/codex-rs/core/src/mcp_tool_call.rs +++ b/codex-rs/core/src/mcp_tool_call.rs @@ -773,6 +773,8 @@ async fn augment_mcp_tool_request_meta_with_sandbox_state( else { return Ok(meta); }; + // TODO(anp): Build this metadata from the server's captured + // TurnEnvironment::sandbox_context instead of the runtime-wide Landlock value. let permission_profile = prepared_call.config().permission_profile.clone(); let sandbox_state = serde_json::to_value(SandboxState { permission_profile, diff --git a/codex-rs/core/src/mcp_tool_call_tests.rs b/codex-rs/core/src/mcp_tool_call_tests.rs index ac9cac8395..9748432eb1 100644 --- a/codex-rs/core/src/mcp_tool_call_tests.rs +++ b/codex-rs/core/src/mcp_tool_call_tests.rs @@ -1113,6 +1113,12 @@ async fn mcp_sandbox_cwd_uses_matching_server_environment_uri() -> anyhow::Resul workspace_roots: Vec::new(), config: EnvironmentConfigState::Ready(EnvironmentConfig { allow_login_shell: true, + windows_sandbox_level: turn_context.windows_sandbox_level, + windows_sandbox_private_desktop: turn_context + .config + .permissions + .windows_sandbox_private_desktop, + use_legacy_landlock: turn_context.config.features.use_legacy_landlock(), permission_profile: turn_context .config .permissions diff --git a/codex-rs/core/src/sandboxing/mod.rs b/codex-rs/core/src/sandboxing/mod.rs index af3647d096..fae02bf120 100644 --- a/codex-rs/core/src/sandboxing/mod.rs +++ b/codex-rs/core/src/sandboxing/mod.rs @@ -60,6 +60,8 @@ pub struct ExecRequest { pub sandbox: SandboxType, pub windows_sandbox_policy_cwd: PathUri, pub windows_sandbox_workspace_roots: Vec, + // TODO(anp): Reconcile these backend copies with TurnEnvironment::sandbox_context + // and exec_server_sandbox so local and remote launches use the same settings. pub windows_sandbox_level: WindowsSandboxLevel, pub windows_sandbox_private_desktop: bool, pub permission_profile: PermissionProfile, diff --git a/codex-rs/core/src/session/mcp.rs b/codex-rs/core/src/session/mcp.rs index cdcc15b573..dcb5c399ba 100644 --- a/codex-rs/core/src/session/mcp.rs +++ b/codex-rs/core/src/session/mcp.rs @@ -1,10 +1,8 @@ use super::mcp_refresh::McpRefreshInvalidationGuard; use super::*; use crate::environment_selection::combine_selected_capability_roots; -use crate::tools::sandboxing::executor_windows_sandbox_level; use codex_exec_server::ExecutorCapabilityDiscoveryCache; use codex_exec_server::ExecutorCapabilityDiscoverySnapshot; -use codex_exec_server::FileSystemSandboxContext; use codex_exec_server::MAX_SELECTED_CAPABILITY_ROOTS; use codex_exec_server::ResolvedSelectedCapabilityRoot; use codex_mcp::CODEX_APPS_MCP_SERVER_NAME; @@ -98,10 +96,9 @@ impl Session { config: &Config, ) -> (McpConfig, McpRuntimeContext) { let originator = self.originator().await; - let (windows_sandbox_level, session_source, host_fallback_cwd) = { + let (session_source, host_fallback_cwd) = { let state = self.state.lock().await; ( - state.session_configuration.windows_sandbox_level, state.session_configuration.session_source.clone(), state.session_configuration.cwd().clone(), ) @@ -117,7 +114,6 @@ impl Session { config, &ready_selected_capability_roots, &environments, - windows_sandbox_level, ) .await; let mcp_projection = self @@ -210,7 +206,6 @@ impl Session { &desired.config, &ready_selected_capability_roots, &desired.environments, - desired.windows_sandbox_level, ) .await; let mcp_projection = self @@ -265,7 +260,6 @@ impl Session { &desired.config, &ready_selected_capability_roots, &desired.environments, - desired.windows_sandbox_level, ) .await; let mcp_projection = self @@ -373,7 +367,6 @@ impl Session { config: &Config, ready_selected_capability_roots: &[SelectedCapabilityRoot], environments: &TurnEnvironmentSnapshot, - windows_sandbox_level: WindowsSandboxLevel, ) -> Option> { // Capability roots can currently be selected independently of turn environments, so a // root may be ready when there is no primary `TurnEnvironment`. Keep using the thread @@ -408,17 +401,10 @@ impl Session { environments .turn_environments() .map(|environment| { - let mut sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd( - environment.permission_profile().clone(), - environment.cwd().clone(), - ); - sandbox.workspace_roots = environment.workspace_roots().to_vec(); - sandbox.windows_sandbox_level = - executor_windows_sandbox_level(windows_sandbox_level, environment.cwd()); - sandbox.windows_sandbox_private_desktop = - config.permissions.windows_sandbox_private_desktop; - sandbox.use_legacy_landlock = config.features.use_legacy_landlock(); - (environment.selection.environment_id.clone(), sandbox) + ( + environment.selection.environment_id.clone(), + environment.sandbox_context(/*additional_permissions*/ None), + ) }) .collect::>() } else { @@ -665,7 +651,6 @@ impl Session { refresh_config, &ready_selected_capability_roots, &environments, - turn_context.windows_sandbox_level, ) .await; let mcp_projection = self diff --git a/codex-rs/core/src/session/mcp_runtime.rs b/codex-rs/core/src/session/mcp_runtime.rs index e569bf1ac2..50b26a762f 100644 --- a/codex-rs/core/src/session/mcp_runtime.rs +++ b/codex-rs/core/src/session/mcp_runtime.rs @@ -28,7 +28,6 @@ pub(super) struct McpDesiredState { pub(super) session_source: SessionSource, pub(super) environments: TurnEnvironmentSnapshot, pub(super) local_process_cwd: PathBuf, - pub(super) windows_sandbox_level: WindowsSandboxLevel, } impl Session { @@ -38,6 +37,9 @@ impl Session { next: &SessionConfiguration, updates: &SessionSettingsUpdate, ) -> bool { + // TODO(anp): Reconcile invalidation with TurnEnvironment::sandbox_context. + // This gap predates that API: an internal Windows-level-only settings update + // can leave the published MCP configuration stale. current.cwd() != next.cwd() || current.approval_policy.value() != next.approval_policy.value() || current.approvals_reviewer != next.approvals_reviewer @@ -97,7 +99,6 @@ impl Session { session_source: session_configuration.session_source.clone(), environments, local_process_cwd, - windows_sandbox_level: session_configuration.windows_sandbox_level, } } @@ -124,7 +125,6 @@ impl Session { session_source: session_configuration.session_source.clone(), environments: resolved_environments.clone(), local_process_cwd, - windows_sandbox_level: session_configuration.windows_sandbox_level, }; self.publish_mcp_runtime( &desired, diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 861079a2b1..d14cadcad9 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -716,6 +716,8 @@ impl Session { allow_login_shell: config.permissions.allow_login_shell, shell_environment_policy: config.permissions.shell_environment_policy.clone(), windows_sandbox_level: WindowsSandboxLevel::from_config(&config), + windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop, + use_legacy_landlock: config.features.use_legacy_landlock(), legacy_fallback_cwd: config.cwd.clone(), codex_home: config.codex_home.clone(), thread_name: None, @@ -3254,11 +3256,7 @@ impl Session { let environments = turn_context.environments.refresh_readiness(); self.services .agents_md_manager - .refresh( - &turn_context.config, - &environments, - turn_context.windows_sandbox_level, - ) + .refresh(&turn_context.config, &environments) .await?; let loaded_agents_md = self.services.agents_md_manager.get_loaded().await; let selected_capability_roots = self @@ -3271,7 +3269,6 @@ impl Session { &turn_context.config, &ready_selected_capability_roots, &environments, - turn_context.windows_sandbox_level, ) .or_cancel(cancellation_token) .await?; @@ -3292,10 +3289,7 @@ impl Session { .map(|environment| { ( environment.selection.environment_id.clone(), - turn_context.file_system_sandbox_context( - /*additional_permissions*/ None, - environment, - ), + environment.sandbox_context(/*additional_permissions*/ None), ) }) .collect::>(); diff --git a/codex-rs/core/src/session/session.rs b/codex-rs/core/src/session/session.rs index 69c7f4be13..350187abeb 100644 --- a/codex-rs/core/src/session/session.rs +++ b/codex-rs/core/src/session/session.rs @@ -97,7 +97,11 @@ pub(crate) struct SessionConfiguration { pub(super) permission_profile_state: PermissionProfileState, pub(super) allow_login_shell: bool, pub(super) shell_environment_policy: ShellEnvironmentPolicy, + // TODO(anp): Reconcile these legacy thread defaults with TurnEnvironment::sandbox_context; + // internal sandbox decisions should use the selected environment's configuration. pub(super) windows_sandbox_level: WindowsSandboxLevel, + pub(super) windows_sandbox_private_desktop: bool, + pub(super) use_legacy_landlock: bool, /// Legacy thread cwd used when a turn does not select an environment. pub(super) legacy_fallback_cwd: AbsolutePathBuf, @@ -144,6 +148,9 @@ impl SessionConfiguration { allow_login_shell: self.allow_login_shell, permission_profile: self.permission_profile_state.snapshot(), shell_environment_policy: self.shell_environment_policy.clone(), + windows_sandbox_level: self.windows_sandbox_level, + windows_sandbox_private_desktop: self.windows_sandbox_private_desktop, + use_legacy_landlock: self.use_legacy_landlock, exec_policy: None, mcp_policy: None, network_policy: None, @@ -293,6 +300,9 @@ impl SessionConfiguration { service_tier: Some(self.service_tier.clone()), collaboration_mode: Some(self.collaboration_mode.clone()), personality: self.personality, + // TODO(anp): Include the runtime Windows sandbox override in restored settings. + // This omission predates TurnEnvironment::sandbox_context; rebuilding FromThread + // environments falls back to Config instead of retaining that override. ..Default::default() } } @@ -1219,11 +1229,7 @@ impl Session { otel.name = "session_init.thread_name_lookup", )); let (agents_md_result, plugin_skill_errors, thread_name) = tokio::join!( - agents_md_manager.refresh( - config.as_ref(), - &resolved_environments, - session_configuration.windows_sandbox_level, - ), + agents_md_manager.refresh(config.as_ref(), &resolved_environments), plugin_skill_warmup, thread_name_lookup, ); diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index e5b49d450c..30b27e78d0 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -32,9 +32,11 @@ use codex_config::loader::project_trust_key; use codex_config::types::McpServerConfig; use codex_config::types::McpServerTransportConfig; use codex_config::types::ToolSuggestDisabledTool; +use codex_config::types::WindowsSandboxModeToml; use core_test_support::test_codex::TurnInputRequest as ExternalTurnInputRequest; use codex_features::Feature; +use codex_file_system::FileSystemSandboxContext; use codex_http_client::ClientRouteClass; use codex_http_client::HttpClientFactory; use codex_http_client::OutboundProxyPolicy; @@ -4340,6 +4342,8 @@ async fn set_rate_limits_retains_previous_credits() { allow_login_shell: config.permissions.allow_login_shell, shell_environment_policy: config.permissions.shell_environment_policy.clone(), windows_sandbox_level: WindowsSandboxLevel::from_config(&config), + windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop, + use_legacy_landlock: config.features.use_legacy_landlock(), legacy_fallback_cwd: config.cwd.clone(), codex_home: config.codex_home.clone(), thread_name: None, @@ -4451,6 +4455,8 @@ async fn set_rate_limits_updates_plan_type_when_present() { allow_login_shell: config.permissions.allow_login_shell, shell_environment_policy: config.permissions.shell_environment_policy.clone(), windows_sandbox_level: WindowsSandboxLevel::from_config(&config), + windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop, + use_legacy_landlock: config.features.use_legacy_landlock(), legacy_fallback_cwd: config.cwd.clone(), codex_home: config.codex_home.clone(), thread_name: None, @@ -5007,6 +5013,8 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati allow_login_shell: config.permissions.allow_login_shell, shell_environment_policy: config.permissions.shell_environment_policy.clone(), windows_sandbox_level: WindowsSandboxLevel::from_config(&config), + windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop, + use_legacy_landlock: config.features.use_legacy_landlock(), legacy_fallback_cwd: config.cwd.clone(), codex_home: config.codex_home.clone(), thread_name: None, @@ -5794,6 +5802,8 @@ async fn session_new_fails_when_zsh_fork_enabled_without_packaged_zsh() { allow_login_shell: config.permissions.allow_login_shell, shell_environment_policy: config.permissions.shell_environment_policy.clone(), windows_sandbox_level: WindowsSandboxLevel::from_config(&config), + windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop, + use_legacy_landlock: config.features.use_legacy_landlock(), legacy_fallback_cwd: config.cwd.clone(), codex_home: config.codex_home.clone(), thread_name: None, @@ -5942,6 +5952,8 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) { allow_login_shell: config.permissions.allow_login_shell, shell_environment_policy: config.permissions.shell_environment_policy.clone(), windows_sandbox_level: WindowsSandboxLevel::from_config(&config), + windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop, + use_legacy_landlock: config.features.use_legacy_landlock(), legacy_fallback_cwd: config.cwd.clone(), codex_home: config.codex_home.clone(), thread_name: None, @@ -6230,6 +6242,8 @@ async fn make_session_with_config_and_rx( allow_login_shell: config.permissions.allow_login_shell, shell_environment_policy: config.permissions.shell_environment_policy.clone(), windows_sandbox_level: WindowsSandboxLevel::from_config(&config), + windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop, + use_legacy_landlock: config.features.use_legacy_landlock(), legacy_fallback_cwd: config.cwd.clone(), codex_home: config.codex_home.clone(), thread_name: None, @@ -6351,6 +6365,8 @@ async fn make_session_with_history_source_and_agent_control_and_rx( allow_login_shell: config.permissions.allow_login_shell, shell_environment_policy: config.permissions.shell_environment_policy.clone(), windows_sandbox_level: WindowsSandboxLevel::from_config(&config), + windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop, + use_legacy_landlock: config.features.use_legacy_landlock(), legacy_fallback_cwd: config.cwd.clone(), codex_home: config.codex_home.clone(), thread_name: None, @@ -8153,6 +8169,8 @@ where allow_login_shell: config.permissions.allow_login_shell, shell_environment_policy: config.permissions.shell_environment_policy.clone(), windows_sandbox_level: WindowsSandboxLevel::from_config(&config), + windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop, + use_legacy_landlock: config.features.use_legacy_landlock(), legacy_fallback_cwd: config.cwd.clone(), codex_home: config.codex_home.clone(), thread_name: None, @@ -8844,13 +8862,23 @@ async fn conflicting_ready_environment_root_ids_keep_first_location() { }); } +/// Capability discovery must use environment-owned permissions and sandbox backends, +/// even when the thread defaults differ. #[tokio::test] async fn capability_discovery_uses_environment_permission_profile() { let (session, mut turn_context) = make_session_and_context().await; - Arc::make_mut(&mut turn_context.config) + let config = Arc::make_mut(&mut turn_context.config); + config .permissions .set_permission_profile(PermissionProfile::Disabled) .expect("unrestricted permission profile should be allowed"); + config.permissions.windows_sandbox_mode = Some(WindowsSandboxModeToml::Unelevated); + config.permissions.windows_sandbox_private_desktop = true; + config + .features + .disable(Feature::UseLegacyLandlock) + .expect("disable legacy Landlock"); + turn_context.windows_sandbox_level = WindowsSandboxLevel::RestrictedToken; let mut environment = turn_context .environments .primary() @@ -8864,13 +8892,24 @@ async fn capability_discovery_uses_environment_permission_profile() { access: FileSystemAccessMode::Deny, missing_path_behavior: None, }); - environment.config_mut().permission_profile = + let environment_config = environment.config_mut(); + environment_config.permission_profile = PermissionProfileSnapshot::legacy(PermissionProfile::from_runtime_permissions( &file_system_policy, NetworkSandboxPolicy::Restricted, )); - let expected_sandbox = turn_context - .file_system_sandbox_context(/*additional_permissions*/ None, &environment); + environment_config.windows_sandbox_level = WindowsSandboxLevel::Elevated; + environment_config.windows_sandbox_private_desktop = false; + environment_config.use_legacy_landlock = true; + let expected_sandbox = FileSystemSandboxContext { + permissions: environment.permission_profile().clone().into(), + cwd: Some(environment.cwd().clone()), + workspace_roots: environment.workspace_roots().to_vec(), + windows_sandbox_level: WindowsSandboxLevel::Elevated, + windows_sandbox_private_desktop: false, + windows_sandbox_proxy_settings_mode: None, + use_legacy_landlock: true, + }; let environment_id = environment.selection.environment_id.clone(); turn_context.environments.environments[0] = TurnEnvironmentState::Ready(environment); @@ -8879,7 +8918,6 @@ async fn capability_discovery_uses_environment_permission_profile() { &turn_context.config, /*ready_selected_capability_roots*/ &[], &turn_context.environments, - turn_context.windows_sandbox_level, ) .await .expect("restricted environment should trigger capability discovery"); diff --git a/codex-rs/core/src/session/turn_context.rs b/codex-rs/core/src/session/turn_context.rs index 11f56de854..5389c652bf 100644 --- a/codex-rs/core/src/session/turn_context.rs +++ b/codex-rs/core/src/session/turn_context.rs @@ -104,6 +104,32 @@ impl TurnEnvironment { self.config().permission_profile.permission_profile() } + /// Sandbox context for this environment, including any additional permission grants. + pub(crate) fn sandbox_context( + &self, + additional_permissions: Option, + ) -> FileSystemSandboxContext { + let config = self.config(); + // Grant-adjusted permissions take precedence over the environment's baseline; + // paths and sandbox backend settings remain environment-owned. + let permissions = effective_permission_profile( + self.permission_profile(), + additional_permissions.as_ref(), + ); + FileSystemSandboxContext { + permissions: permissions.into(), + cwd: Some(self.cwd().clone()), + workspace_roots: self.workspace_roots().to_vec(), + windows_sandbox_level: executor_windows_sandbox_level( + config.windows_sandbox_level, + self.cwd(), + ), + windows_sandbox_private_desktop: config.windows_sandbox_private_desktop, + windows_sandbox_proxy_settings_mode: None, + use_legacy_landlock: config.use_legacy_landlock, + } + } + pub(crate) fn active_permission_profile(&self) -> Option { self.config().permission_profile.active_permission_profile() } @@ -178,6 +204,8 @@ pub struct TurnContext { pub(crate) multi_agent_version: MultiAgentVersion, pub(crate) personality: Option, pub(crate) network: Option, + // TODO(anp): Reconcile this parallel turn snapshot with TurnEnvironment::sandbox_context + // so owner-provided environment settings govern the remaining sandbox decisions. pub(crate) windows_sandbox_level: WindowsSandboxLevel, pub(crate) available_models: Vec, pub(crate) unified_exec_shell_mode: UnifiedExecShellMode, @@ -449,32 +477,6 @@ impl TurnContext { } } - pub(crate) fn file_system_sandbox_context( - &self, - additional_permissions: Option, - environment: &TurnEnvironment, - ) -> FileSystemSandboxContext { - let permissions = effective_permission_profile( - environment.permission_profile(), - additional_permissions.as_ref(), - ); - FileSystemSandboxContext { - permissions: permissions.into(), - cwd: Some(environment.cwd().clone()), - workspace_roots: environment.workspace_roots().to_vec(), - windows_sandbox_level: executor_windows_sandbox_level( - self.windows_sandbox_level, - environment.cwd(), - ), - windows_sandbox_private_desktop: self - .config - .permissions - .windows_sandbox_private_desktop, - windows_sandbox_proxy_settings_mode: None, - use_legacy_landlock: self.config.features.use_legacy_landlock(), - } - } - fn non_legacy_file_system_sandbox_policy(&self) -> Option { // Omit the derived split filesystem policy when it is equivalent to // the legacy sandbox policy. This keeps turn-context payloads stable diff --git a/codex-rs/core/src/thread_manager_tests.rs b/codex-rs/core/src/thread_manager_tests.rs index fea6e44ecc..58bf026bdc 100644 --- a/codex-rs/core/src/thread_manager_tests.rs +++ b/codex-rs/core/src/thread_manager_tests.rs @@ -11,6 +11,7 @@ use crate::session::tests::build_world_state_from_turn_context; use crate::session::tests::make_session_and_context; use crate::tasks::InterruptedTurnHistoryMarker; use crate::tasks::interrupted_turn_history_marker; +use crate::windows_sandbox::WindowsSandboxLevelExt; use codex_extension_api::empty_extension_registry; use codex_history::InitialHistory; use codex_history::ResumedHistory; @@ -18,6 +19,7 @@ use codex_models_manager::manager::RefreshStrategy; use codex_protocol::ResponseItemId; use codex_protocol::capabilities::CapabilityRootLocation; use codex_protocol::capabilities::SelectedCapabilityRoot; +use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::mcp::ClientMcpExtensions; use codex_protocol::mcp::MCP_APP_UI_EXTENSION_ID; use codex_protocol::mcp::OPENAI_FORM_EXTENSION_ID; @@ -1017,6 +1019,9 @@ async fn spawn_internal_session_preserves_parent_lineage_without_forking_history allow_login_shell: true, permission_profile: config.permissions.permission_profile_state().snapshot(), shell_environment_policy: Default::default(), + windows_sandbox_level: WindowsSandboxLevel::from_config(&config), + windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop, + use_legacy_landlock: config.features.use_legacy_landlock(), exec_policy: Some(codex_execpolicy::RequirementsExecPolicy::new( codex_execpolicy::Policy::empty(), )), diff --git a/codex-rs/core/src/tools/handlers/apply_patch.rs b/codex-rs/core/src/tools/handlers/apply_patch.rs index b474e661ae..484e201156 100644 --- a/codex-rs/core/src/tools/handlers/apply_patch.rs +++ b/codex-rs/core/src/tools/handlers/apply_patch.rs @@ -406,8 +406,7 @@ impl ApplyPatchHandler { )); }; let fs = turn_environment.environment.get_filesystem(); - let sandbox = turn - .file_system_sandbox_context(/*additional_permissions*/ None, turn_environment); + let sandbox = turn_environment.sandbox_context(/*additional_permissions*/ None); match codex_apply_patch::verify_apply_patch_args_with_mode( args, turn_environment.cwd(), @@ -518,8 +517,7 @@ pub(crate) async fn intercept_apply_patch( tool_name: &str, ) -> Result, FunctionCallError> { let turn = &step_context.turn; - let sandbox = - turn.file_system_sandbox_context(/*additional_permissions*/ None, &turn_environment); + let sandbox = turn_environment.sandbox_context(/*additional_permissions*/ None); match codex_apply_patch::maybe_parse_apply_patch_verified_with_mode( command, cwd, diff --git a/codex-rs/core/src/tools/handlers/extension_tools.rs b/codex-rs/core/src/tools/handlers/extension_tools.rs index 61777018a9..ec825c774e 100644 --- a/codex-rs/core/src/tools/handlers/extension_tools.rs +++ b/codex-rs/core/src/tools/handlers/extension_tools.rs @@ -182,9 +182,7 @@ async fn to_extension_call(invocation: &ToolInvocation) -> ExtensionToolCall { ) .await .additional_permissions; - let file_system_sandbox_context = invocation - .turn - .file_system_sandbox_context(additional_permissions, environment); + let file_system_sandbox_context = environment.sandbox_context(additional_permissions); environments.push(ToolEnvironment { environment_id: environment.selection.environment_id.clone(), cwd: native_cwd, diff --git a/codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs b/codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs index 34ca82e138..9c6c17c43b 100644 --- a/codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs +++ b/codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs @@ -161,6 +161,8 @@ impl ExecCommandHandler { // Remote executors enforce URI-native sandbox policy themselves. Only a host-local // sandbox needs a native cwd for resolving paths nested in the permissions config. + // TODO(anp): Reconcile this backend choice with TurnEnvironment::sandbox_context + // so the native-cwd requirement follows the selected environment's sandbox. let requires_host_native_cwd = !environment.is_remote() && SandboxManager::new().select_initial( turn_environment.permission_profile(), diff --git a/codex-rs/core/src/tools/handlers/view_image.rs b/codex-rs/core/src/tools/handlers/view_image.rs index 22ed341590..aa47ddd359 100644 --- a/codex-rs/core/src/tools/handlers/view_image.rs +++ b/codex-rs/core/src/tools/handlers/view_image.rs @@ -151,8 +151,7 @@ impl ViewImageHandler { )) })?; let model_visible_path = path_uri.inferred_native_path_string(); - let sandbox = turn - .file_system_sandbox_context(/*additional_permissions*/ None, turn_environment); + let sandbox = turn_environment.sandbox_context(/*additional_permissions*/ None); let fs = turn_environment.environment.get_filesystem(); let metadata = fs diff --git a/codex-rs/core/src/tools/orchestrator.rs b/codex-rs/core/src/tools/orchestrator.rs index 184757bb14..fbd57ebba0 100644 --- a/codex-rs/core/src/tools/orchestrator.rs +++ b/codex-rs/core/src/tools/orchestrator.rs @@ -269,6 +269,8 @@ impl ToolOrchestrator { managed_network_active, ), }; + // TODO(anp): Reconcile backend selection and both attempts with + // TurnEnvironment::sandbox_context; turn-wide inputs can differ from owner config. let initial_sandbox = if sandbox_requested && !executor_managed_process_sandbox { self.sandbox.select_initial( &permissions, diff --git a/codex-rs/core/src/tools/registry.rs b/codex-rs/core/src/tools/registry.rs index eeba3b9455..0e73eea102 100644 --- a/codex-rs/core/src/tools/registry.rs +++ b/codex-rs/core/src/tools/registry.rs @@ -485,6 +485,8 @@ impl ToolRegistry { let call_id_owned = invocation.call_id.clone(); let otel = invocation.turn.session_telemetry.clone(); let permission_profile = invocation.turn.permission_profile(); + // TODO(anp): Reconcile these tags with TurnEnvironment::sandbox_context + // instead of reporting the thread-wide backend for environment-scoped tools. let base_tool_result_tags = [ ( "sandbox", diff --git a/codex-rs/core/src/tools/runtimes/apply_patch_tests.rs b/codex-rs/core/src/tools/runtimes/apply_patch_tests.rs index d4b33da16e..8ca3883e65 100644 --- a/codex-rs/core/src/tools/runtimes/apply_patch_tests.rs +++ b/codex-rs/core/src/tools/runtimes/apply_patch_tests.rs @@ -27,6 +27,9 @@ fn test_turn_environment(environment_id: &str) -> crate::session::turn_context:: workspace_roots: Vec::new(), config: EnvironmentConfigState::Ready(EnvironmentConfig { allow_login_shell: true, + windows_sandbox_level: WindowsSandboxLevel::Disabled, + windows_sandbox_private_desktop: true, + use_legacy_landlock: false, permission_profile: PermissionProfileSnapshot::legacy( PermissionProfile::read_only(), ), diff --git a/codex-rs/core/src/tools/runtimes/unified_exec.rs b/codex-rs/core/src/tools/runtimes/unified_exec.rs index 84144f674d..67c2418b55 100644 --- a/codex-rs/core/src/tools/runtimes/unified_exec.rs +++ b/codex-rs/core/src/tools/runtimes/unified_exec.rs @@ -530,6 +530,7 @@ mod tests { use crate::tools::sandboxing::ToolRuntime; use codex_exec_server::Environment; use codex_exec_server::LOCAL_ENVIRONMENT_ID; + use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::PermissionProfile; use codex_protocol::protocol::EnvironmentConfig; use codex_protocol::protocol::EnvironmentConfigState; @@ -550,6 +551,9 @@ mod tests { workspace_roots: Vec::new(), config: EnvironmentConfigState::Ready(EnvironmentConfig { allow_login_shell: true, + windows_sandbox_level: WindowsSandboxLevel::Disabled, + windows_sandbox_private_desktop: true, + use_legacy_landlock: false, permission_profile: PermissionProfileSnapshot::legacy( PermissionProfile::read_only(), ), diff --git a/codex-rs/core/src/tools/runtimes/zsh_fork/unix_escalation.rs b/codex-rs/core/src/tools/runtimes/zsh_fork/unix_escalation.rs index 631bf4c6db..87f8521cfd 100644 --- a/codex-rs/core/src/tools/runtimes/zsh_fork/unix_escalation.rs +++ b/codex-rs/core/src/tools/runtimes/zsh_fork/unix_escalation.rs @@ -532,6 +532,8 @@ fn evaluate_intercepted_exec_policy( struct InterceptedExecPolicyContext { approval_policy: AskForApproval, permission_profile: PermissionProfile, + // TODO(anp): Reconcile this policy input with TurnEnvironment::sandbox_context + // so intercepted commands use the selected environment's Windows backend. windows_sandbox_level: WindowsSandboxLevel, sandbox_permissions: SandboxPermissions, enable_shell_wrapper_parsing: bool, @@ -557,6 +559,8 @@ fn commands_for_intercepted_exec_policy( vec![join_program_and_argv(program, argv)] } +// TODO(anp): Capture these Windows and Landlock settings from +// TurnEnvironment::sandbox_context when preparing this executor, preserving its snapshot. struct CoreShellCommandExecutor { command: Vec, cwd: AbsolutePathBuf, diff --git a/codex-rs/core/src/tools/sandboxing.rs b/codex-rs/core/src/tools/sandboxing.rs index 0ec30aa9fb..aa665c4633 100644 --- a/codex-rs/core/src/tools/sandboxing.rs +++ b/codex-rs/core/src/tools/sandboxing.rs @@ -395,6 +395,8 @@ pub(crate) struct SandboxAttempt<'a> { pub(crate) sandbox_cwd: &'a PathUri, pub(crate) workspace_roots: &'a [PathUri], pub codex_linux_sandbox_exe: Option<&'a std::path::PathBuf>, + // TODO(anp): Reconcile these attempt settings with TurnEnvironment::sandbox_context + // so process execution and patch writes honor the selected environment's backend. pub use_legacy_landlock: bool, pub windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel, pub windows_sandbox_private_desktop: bool, diff --git a/codex-rs/core/src/tools/spec_plan_tests.rs b/codex-rs/core/src/tools/spec_plan_tests.rs index a476cf1518..b5be3fbf08 100644 --- a/codex-rs/core/src/tools/spec_plan_tests.rs +++ b/codex-rs/core/src/tools/spec_plan_tests.rs @@ -1070,6 +1070,12 @@ async fn zsh_fork_unified_exec_keeps_shell_parameter_when_remote_environment_ava config: EnvironmentConfigState::Ready( codex_protocol::protocol::EnvironmentConfig { allow_login_shell: true, + windows_sandbox_level: turn.windows_sandbox_level, + windows_sandbox_private_desktop: turn + .config + .permissions + .windows_sandbox_private_desktop, + use_legacy_landlock: turn.config.features.use_legacy_landlock(), permission_profile: turn .config .permissions diff --git a/codex-rs/core/src/turn_metadata.rs b/codex-rs/core/src/turn_metadata.rs index 478afaa53d..51face5f2c 100644 --- a/codex-rs/core/src/turn_metadata.rs +++ b/codex-rs/core/src/turn_metadata.rs @@ -114,6 +114,8 @@ pub(crate) struct TurnMetadataState { subagent_kind: Option, thread_source: Option, turn_id: String, + // TODO(anp): Derive this cached tag from TurnEnvironment::sandbox_context + // so metadata reflects the selected environment's backend. sandbox: Option, sandbox_mode: Option, auto_review_enabled: bool, diff --git a/codex-rs/core/tests/suite/agent_execution.rs b/codex-rs/core/tests/suite/agent_execution.rs index 562334d989..c7710707be 100644 --- a/codex-rs/core/tests/suite/agent_execution.rs +++ b/codex-rs/core/tests/suite/agent_execution.rs @@ -1,6 +1,8 @@ use anyhow::Result; +use codex_core::windows_sandbox::WindowsSandboxLevelExt; use codex_exec_server::CreateDirectoryOptions; use codex_features::Feature; +use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::PermissionProfile; use codex_protocol::models::PermissionProfileSnapshot; use codex_protocol::protocol::EnvironmentConfig; @@ -312,6 +314,12 @@ async fn v2_residency_reload_preserves_inherited_environment_and_tools( allow_login_shell: test.config.permissions.allow_login_shell, permission_profile: PermissionProfileSnapshot::legacy(child_permissions), shell_environment_policy: Default::default(), + windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config), + windows_sandbox_private_desktop: test + .config + .permissions + .windows_sandbox_private_desktop, + use_legacy_landlock: test.config.features.use_legacy_landlock(), exec_policy: None, mcp_policy: None, network_policy: None, diff --git a/codex-rs/core/tests/suite/exec_policy.rs b/codex-rs/core/tests/suite/exec_policy.rs index ed80250f09..8603aa7ea8 100644 --- a/codex-rs/core/tests/suite/exec_policy.rs +++ b/codex-rs/core/tests/suite/exec_policy.rs @@ -5,6 +5,7 @@ use codex_config::test_support::CloudConfigBundleFixture; use codex_core::EnvironmentConfig; use codex_core::TurnInputRequest; use codex_core::config::Constrained; +use codex_core::windows_sandbox::WindowsSandboxLevelExt; use codex_execpolicy::Decision; use codex_execpolicy::Policy; use codex_execpolicy::RequirementsExecPolicy; @@ -13,6 +14,7 @@ use codex_protocol::config_types::ApprovalsReviewer; use codex_protocol::config_types::CollaborationMode; use codex_protocol::config_types::ModeKind; use codex_protocol::config_types::Settings; +use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::error::CodexErrorDetails; use codex_protocol::models::PermissionProfile; use codex_protocol::models::PermissionProfileSnapshot; @@ -716,6 +718,12 @@ async fn environment_command_restrictions_override_saved_prefix_approvals() -> R allow_login_shell: true, permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled), shell_environment_policy: Default::default(), + windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config), + windows_sandbox_private_desktop: test + .config + .permissions + .windows_sandbox_private_desktop, + use_legacy_landlock: test.config.features.use_legacy_landlock(), exec_policy: Some(RequirementsExecPolicy::new(invalid_policy)), mcp_policy: None, network_policy: None, @@ -739,6 +747,12 @@ async fn environment_command_restrictions_override_saved_prefix_approvals() -> R allow_login_shell: true, permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled), shell_environment_policy: Default::default(), + windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config), + windows_sandbox_private_desktop: test + .config + .permissions + .windows_sandbox_private_desktop, + use_legacy_landlock: test.config.features.use_legacy_landlock(), exec_policy: Some(RequirementsExecPolicy::new(environment_policy)), mcp_policy: None, network_policy: None, @@ -843,6 +857,12 @@ async fn environment_command_policy_changes_invalidate_session_approvals() -> Re PermissionProfile::Disabled, ), shell_environment_policy: Default::default(), + windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config), + windows_sandbox_private_desktop: test + .config + .permissions + .windows_sandbox_private_desktop, + use_legacy_landlock: test.config.features.use_legacy_landlock(), exec_policy: Some(RequirementsExecPolicy::new(policy)), mcp_policy: None, network_policy: None, diff --git a/codex-rs/core/tests/suite/guardian_review.rs b/codex-rs/core/tests/suite/guardian_review.rs index c2429b25f1..1a0cf9269b 100644 --- a/codex-rs/core/tests/suite/guardian_review.rs +++ b/codex-rs/core/tests/suite/guardian_review.rs @@ -14,6 +14,7 @@ use codex_core::config::Config; use codex_core::config::Constrained; use codex_core::config::CurrentTimeReminderConfig; use codex_core::sandboxing::SandboxPermissions; +use codex_core::windows_sandbox::WindowsSandboxLevelExt; use codex_extension_api::ExtensionRegistryBuilder; use codex_extension_api::ToolLifecycleContributor; use codex_extension_api::ToolLifecycleFuture; @@ -25,6 +26,7 @@ use codex_history::RolloutLine; use codex_login::CodexAuth; use codex_protocol::ThreadId; use codex_protocol::config_types::ApprovalsReviewer; +use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::PermissionProfile; use codex_protocol::models::PermissionProfileSnapshot; use codex_protocol::openai_models::AutoReviewMessages; @@ -768,6 +770,9 @@ async fn guardian_session_is_reused_for_consecutive_tool_reviews_without_prewarm test.config.permissions.permission_profile().clone(), ), shell_environment_policy: Default::default(), + windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config), + windows_sandbox_private_desktop: test.config.permissions.windows_sandbox_private_desktop, + use_legacy_landlock: test.config.features.use_legacy_landlock(), exec_policy: None, mcp_policy: None, network_policy: None, diff --git a/codex-rs/core/tests/suite/hooks_executor.rs b/codex-rs/core/tests/suite/hooks_executor.rs index d664d41c9a..a16a0f7e0e 100644 --- a/codex-rs/core/tests/suite/hooks_executor.rs +++ b/codex-rs/core/tests/suite/hooks_executor.rs @@ -2,11 +2,13 @@ use anyhow::Context; use anyhow::Result; use codex_config::McpServerConfig; use codex_core::EnvironmentConfig; +use codex_core::windows_sandbox::WindowsSandboxLevelExt; use codex_exec_server::CreateDirectoryOptions; use codex_exec_server::ExecServerRuntimePaths; use codex_features::Feature; use codex_protocol::capabilities::CapabilityRootLocation; use codex_protocol::capabilities::SelectedCapabilityRoot; +use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::PermissionProfileSnapshot; use codex_protocol::protocol::EnvironmentConfigState; use codex_protocol::protocol::ThreadSettingsOverrides; @@ -400,6 +402,13 @@ impl ExecutorStopHookFixture { self.test.config.permissions.permission_profile().clone(), ), shell_environment_policy: Default::default(), + windows_sandbox_level: WindowsSandboxLevel::from_config(&self.test.config), + windows_sandbox_private_desktop: self + .test + .config + .permissions + .windows_sandbox_private_desktop, + use_legacy_landlock: self.test.config.features.use_legacy_landlock(), exec_policy: None, mcp_policy: None, network_policy: None, diff --git a/codex-rs/core/tests/suite/mcp_startup_refresh_http_proxy.rs b/codex-rs/core/tests/suite/mcp_startup_refresh_http_proxy.rs index fff2535394..0e8dd9d6d7 100644 --- a/codex-rs/core/tests/suite/mcp_startup_refresh_http_proxy.rs +++ b/codex-rs/core/tests/suite/mcp_startup_refresh_http_proxy.rs @@ -1,6 +1,7 @@ use codex_core::EnvironmentConfig; use codex_core::EnvironmentMcpPolicy; use codex_core::TurnInputRequest; +use codex_core::windows_sandbox::WindowsSandboxLevelExt; use std::collections::BTreeMap; use std::collections::HashMap; use std::time::Duration; @@ -12,6 +13,7 @@ use codex_config::McpServerOAuthConfig; use codex_config::McpServerTransportConfig; use codex_exec_server::CreateDirectoryOptions; use codex_features::Feature; +use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::mcp_policy::McpServerIdentity; use codex_protocol::mcp_policy::McpServerRequirement; use codex_protocol::models::PermissionProfile; @@ -362,6 +364,9 @@ async fn skill_mcp_dependency_oauth_uses_configured_http_client() -> Result<()> allow_login_shell: fixture.config.permissions.allow_login_shell, permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled), shell_environment_policy: Default::default(), + windows_sandbox_level: WindowsSandboxLevel::from_config(&fixture.config), + windows_sandbox_private_desktop: fixture.config.permissions.windows_sandbox_private_desktop, + use_legacy_landlock: fixture.config.features.use_legacy_landlock(), exec_policy: None, mcp_policy: Some(EnvironmentMcpPolicy { servers: Some(BTreeMap::from([( diff --git a/codex-rs/core/tests/suite/mcp_tool_exposure.rs b/codex-rs/core/tests/suite/mcp_tool_exposure.rs index deb09b0ace..0b07704e3e 100644 --- a/codex-rs/core/tests/suite/mcp_tool_exposure.rs +++ b/codex-rs/core/tests/suite/mcp_tool_exposure.rs @@ -3,6 +3,7 @@ use codex_config::Constrained; use codex_core::EnvironmentConfig; use codex_core::TurnInputRequest; use codex_core::config::Config; +use codex_core::windows_sandbox::WindowsSandboxLevelExt; use codex_extension_api::ExtensionFuture; use codex_extension_api::ExtensionRegistryBuilder; use codex_extension_api::McpServerContribution; @@ -17,6 +18,7 @@ use codex_mcp::CODEX_APPS_MCP_SERVER_NAME; use codex_mcp::McpResourceClient; use codex_protocol::capabilities::CapabilityRootLocation; use codex_protocol::capabilities::SelectedCapabilityRoot; +use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::PermissionProfile; use codex_protocol::models::PermissionProfileSnapshot; use codex_protocol::protocol::AskForApproval; @@ -491,6 +493,12 @@ async fn root_reconciliation_reuses_pending_apps_startup() -> Result<()> { test.config.permissions.permission_profile().clone(), ), shell_environment_policy: Default::default(), + windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config), + windows_sandbox_private_desktop: test + .config + .permissions + .windows_sandbox_private_desktop, + use_legacy_landlock: test.config.features.use_legacy_landlock(), exec_policy: None, mcp_policy: None, network_policy: None, diff --git a/codex-rs/core/tests/suite/network_approval.rs b/codex-rs/core/tests/suite/network_approval.rs index 4de596b850..d02c16bf74 100644 --- a/codex-rs/core/tests/suite/network_approval.rs +++ b/codex-rs/core/tests/suite/network_approval.rs @@ -8,6 +8,7 @@ use codex_core::config::Constrained; use codex_core::config::NetworkProxySpec; use codex_core::shell::ShellType; use codex_core::shell::get_shell; +use codex_core::windows_sandbox::WindowsSandboxLevelExt; use codex_exec_server::CreateDirectoryOptions; use codex_exec_server::LOCAL_ENVIRONMENT_ID; use codex_exec_server::REMOTE_ENVIRONMENT_ID; @@ -21,6 +22,7 @@ use codex_protocol::approvals::NetworkPolicyRuleAction; use codex_protocol::config_types::CollaborationMode; use codex_protocol::config_types::ModeKind; use codex_protocol::config_types::Settings; +use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::NetworkPermissions; use codex_protocol::models::PermissionProfile; use codex_protocol::models::PermissionProfileSnapshot; @@ -2024,6 +2026,12 @@ async fn owner_network_policy_follows_the_selected_remote_command() -> Result<() test.config.permissions.permission_profile().clone() }), shell_environment_policy: test.config.permissions.shell_environment_policy.clone(), + windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config), + windows_sandbox_private_desktop: test + .config + .permissions + .windows_sandbox_private_desktop, + use_legacy_landlock: test.config.features.use_legacy_landlock(), exec_policy: None, mcp_policy: None, network_policy: Some(EnvironmentNetworkPolicy::from_config( diff --git a/codex-rs/core/tests/suite/remote_env.rs b/codex-rs/core/tests/suite/remote_env.rs index 6e0c3161e1..73d243b631 100644 --- a/codex-rs/core/tests/suite/remote_env.rs +++ b/codex-rs/core/tests/suite/remote_env.rs @@ -13,6 +13,7 @@ use codex_core::WaitForEnvironmentToolConfig; use codex_core::compact::SUMMARIZATION_PROMPT; use codex_core::config::Config; use codex_core::config::Constrained; +use codex_core::windows_sandbox::WindowsSandboxLevelExt; use codex_exec_server::CopyOptions; use codex_exec_server::CreateDirectoryOptions; use codex_exec_server::EnvironmentReadyInfo; @@ -46,6 +47,7 @@ use codex_protocol::capabilities::SelectedCapabilityRoot; use codex_protocol::config_types::CollaborationMode; use codex_protocol::config_types::ModeKind; use codex_protocol::config_types::Settings; +use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::ActivePermissionProfile; use codex_protocol::models::FileSystemPermissions; use codex_protocol::models::PermissionProfile; @@ -557,6 +559,12 @@ async fn environment_permissions_follow_configuration_ownership() -> Result<()> allow_login_shell: test.config.permissions.allow_login_shell, permission_profile: owner_permission_profile, shell_environment_policy: Default::default(), + windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config), + windows_sandbox_private_desktop: test + .config + .permissions + .windows_sandbox_private_desktop, + use_legacy_landlock: test.config.features.use_legacy_landlock(), exec_policy: None, mcp_policy: None, network_policy: None, @@ -1246,6 +1254,12 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment( allow_login_shell: false, permission_profile: permission_profile.clone(), shell_environment_policy: Default::default(), + windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config), + windows_sandbox_private_desktop: test + .config + .permissions + .windows_sandbox_private_desktop, + use_legacy_landlock: test.config.features.use_legacy_landlock(), exec_policy: None, mcp_policy: None, network_policy: None, @@ -1284,6 +1298,12 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment( allow_login_shell: true, permission_profile: permission_profile.clone(), shell_environment_policy: Default::default(), + windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config), + windows_sandbox_private_desktop: test + .config + .permissions + .windows_sandbox_private_desktop, + use_legacy_landlock: test.config.features.use_legacy_landlock(), exec_policy: None, mcp_policy: None, network_policy: None, @@ -1306,6 +1326,12 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment( allow_login_shell: false, permission_profile: permission_profile.clone(), shell_environment_policy: Default::default(), + windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config), + windows_sandbox_private_desktop: test + .config + .permissions + .windows_sandbox_private_desktop, + use_legacy_landlock: test.config.features.use_legacy_landlock(), exec_policy: None, mcp_policy: None, network_policy: None, @@ -1367,6 +1393,12 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment( allow_login_shell: false, permission_profile: permission_profile.clone(), shell_environment_policy: Default::default(), + windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config), + windows_sandbox_private_desktop: test + .config + .permissions + .windows_sandbox_private_desktop, + use_legacy_landlock: test.config.features.use_legacy_landlock(), exec_policy: None, mcp_policy: None, network_policy: None, @@ -1436,6 +1468,9 @@ async fn owner_network_policy_rejects_unsupported_environment_authority() -> Res allow_login_shell: test.config.permissions.allow_login_shell, permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled), shell_environment_policy: test.config.permissions.shell_environment_policy.clone(), + windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config), + windows_sandbox_private_desktop: test.config.permissions.windows_sandbox_private_desktop, + use_legacy_landlock: test.config.features.use_legacy_landlock(), exec_policy: None, mcp_policy: None, network_policy: Some(EnvironmentNetworkPolicy::from_config( @@ -1520,6 +1555,9 @@ async fn pending_attachment_installs_configuration_before_waiting_turn_resumes() allow_login_shell, permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::read_only()), shell_environment_policy: Default::default(), + windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config), + windows_sandbox_private_desktop: test.config.permissions.windows_sandbox_private_desktop, + use_legacy_landlock: test.config.features.use_legacy_landlock(), exec_policy: None, mcp_policy: None, network_policy: None, @@ -2174,6 +2212,12 @@ async fn deferred_executor_spawn_agent_inherits_ready_step_environments( vec![owner_profile_workspace_root.clone()], ), shell_environment_policy: Default::default(), + windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config), + windows_sandbox_private_desktop: test + .config + .permissions + .windows_sandbox_private_desktop, + use_legacy_landlock: test.config.features.use_legacy_landlock(), exec_policy: None, mcp_policy: None, network_policy: None, diff --git a/codex-rs/core/tests/suite/rmcp_client.rs b/codex-rs/core/tests/suite/rmcp_client.rs index 3897f43966..a9def0d0fc 100644 --- a/codex-rs/core/tests/suite/rmcp_client.rs +++ b/codex-rs/core/tests/suite/rmcp_client.rs @@ -27,6 +27,7 @@ use codex_core::EnvironmentConfig; use codex_core::EnvironmentMcpPolicy; use codex_core::TurnInputRequest; use codex_core::config::Config; +use codex_core::windows_sandbox::WindowsSandboxLevelExt; use codex_exec_server::CreateDirectoryOptions; use codex_exec_server::Environment; use codex_exec_server::HttpRedirectPolicy; @@ -45,6 +46,7 @@ use codex_protocol::config_types::CollaborationMode; use codex_protocol::config_types::ModeKind; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::config_types::Settings; +use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::mcp_policy::McpServerIdentity; use codex_protocol::mcp_policy::McpServerRequirement; use codex_protocol::mcp_policy::PluginMcpRequirements; @@ -712,6 +714,12 @@ async fn environment_mcp_policy_filters_runtime_config_and_model_tools( fixture.config.permissions.permission_profile().clone(), ), shell_environment_policy: Default::default(), + windows_sandbox_level: WindowsSandboxLevel::from_config(&fixture.config), + windows_sandbox_private_desktop: fixture + .config + .permissions + .windows_sandbox_private_desktop, + use_legacy_landlock: fixture.config.features.use_legacy_landlock(), exec_policy: None, mcp_policy: Some(mcp_policy), network_policy: None, diff --git a/codex-rs/core/tests/suite/unified_exec.rs b/codex-rs/core/tests/suite/unified_exec.rs index cb35cd03da..8976088ed3 100644 --- a/codex-rs/core/tests/suite/unified_exec.rs +++ b/codex-rs/core/tests/suite/unified_exec.rs @@ -1,5 +1,6 @@ use codex_core::EnvironmentConfig; use codex_core::TurnInputRequest; +use codex_core::windows_sandbox::WindowsSandboxLevelExt; use core_test_support::test_codex::local_selections; use std::collections::HashMap; use std::ffi::OsStr; @@ -15,6 +16,7 @@ use codex_protocol::config_types::ModeKind; use codex_protocol::config_types::Settings; use codex_protocol::config_types::ShellEnvironmentPolicy; use codex_protocol::config_types::ShellEnvironmentPolicyInherit; +use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::PermissionProfile; use codex_protocol::models::PermissionProfileSnapshot; use codex_protocol::models::ResponseItem; @@ -402,6 +404,13 @@ async fn exec_command_uses_installed_environment_shell_policy_with_explicit_over .clone(), ..Default::default() }, + windows_sandbox_level: WindowsSandboxLevel::from_config(&harness.test().config), + windows_sandbox_private_desktop: harness + .test() + .config + .permissions + .windows_sandbox_private_desktop, + use_legacy_landlock: harness.test().config.features.use_legacy_landlock(), exec_policy: None, mcp_policy: None, network_policy: None, diff --git a/codex-rs/protocol/src/environment.rs b/codex-rs/protocol/src/environment.rs index 21e7275ee3..17a1ba3e6a 100644 --- a/codex-rs/protocol/src/environment.rs +++ b/codex-rs/protocol/src/environment.rs @@ -1,5 +1,6 @@ use crate::capabilities::SelectedCapabilityRoot; use crate::config_types::ShellEnvironmentPolicy; +use crate::config_types::WindowsSandboxLevel; use crate::mcp_policy::EnvironmentMcpPolicy; use crate::models::PermissionProfileSnapshot; use codex_execpolicy::RequirementsExecPolicy; @@ -28,6 +29,12 @@ pub struct EnvironmentConfig { pub permission_profile: PermissionProfileSnapshot, /// Controls which environment variables shell commands may inherit. pub shell_environment_policy: ShellEnvironmentPolicy, + /// Windows sandbox implementation for this environment attachment. + pub windows_sandbox_level: WindowsSandboxLevel, + /// Whether Windows sandbox processes use a private desktop. + pub windows_sandbox_private_desktop: bool, + /// Whether Linux sandbox processes use the legacy Landlock backend. + pub use_legacy_landlock: bool, /// Additional managed command restrictions for this environment attachment. pub exec_policy: Option, /// Additional managed MCP restrictions for this environment attachment. @@ -45,6 +52,12 @@ impl std::fmt::Debug for EnvironmentConfig { .field("allow_login_shell", &self.allow_login_shell) .field("permission_profile", &self.permission_profile) .field("shell_environment_policy", &"") + .field("windows_sandbox_level", &self.windows_sandbox_level) + .field( + "windows_sandbox_private_desktop", + &self.windows_sandbox_private_desktop, + ) + .field("use_legacy_landlock", &self.use_legacy_landlock) .field("exec_policy", &self.exec_policy) .field("mcp_policy", &self.mcp_policy) .field("network_policy", &self.network_policy) diff --git a/codex-rs/sandboxing/src/manager.rs b/codex-rs/sandboxing/src/manager.rs index 6ffef8a6f1..507841b3b7 100644 --- a/codex-rs/sandboxing/src/manager.rs +++ b/codex-rs/sandboxing/src/manager.rs @@ -119,6 +119,8 @@ pub struct SandboxExecRequest { pub network: Option, pub network_environment_id: Option, pub sandbox: SandboxType, + // TODO(anp): Reconcile these backend copies with the supplied sandbox context + // (TurnEnvironment::sandbox_context for turns), preserving this launch snapshot. pub windows_sandbox_level: WindowsSandboxLevel, pub windows_sandbox_private_desktop: bool, pub permission_profile: PermissionProfile, @@ -139,6 +141,8 @@ pub struct SandboxTransformRequest<'a> { pub network: Option<&'a NetworkProxy>, pub sandbox_policy_cwd: &'a PathUri, pub codex_linux_sandbox_exe: Option<&'a Path>, + // TODO(anp): Reconcile these backend inputs with the supplied sandbox context + // (TurnEnvironment::sandbox_context for turns) so selection shares its authority. pub use_legacy_landlock: bool, pub windows_sandbox_level: WindowsSandboxLevel, pub windows_sandbox_private_desktop: bool, diff --git a/codex-rs/sandboxing/src/spawn.rs b/codex-rs/sandboxing/src/spawn.rs index 5766c2b167..7054e88c77 100644 --- a/codex-rs/sandboxing/src/spawn.rs +++ b/codex-rs/sandboxing/src/spawn.rs @@ -14,6 +14,8 @@ use crate::WindowsSandboxFilesystemOverrides; use crate::WindowsSandboxProxySettingsMode; /// Windows-specific inputs for an executor-native process spawn. +// TODO(anp): Reconcile Windows backend and desktop copies with the supplied sandbox +// context (TurnEnvironment::sandbox_context for turns), preserving this launch snapshot. pub struct WindowsSandboxSpawnRequest<'a> { pub permission_profile: &'a PermissionProfile, pub workspace_roots: &'a [AbsolutePathBuf], diff --git a/codex-rs/tui/src/app/thread_settings.rs b/codex-rs/tui/src/app/thread_settings.rs index 5f1e7e83ec..58383388b7 100644 --- a/codex-rs/tui/src/app/thread_settings.rs +++ b/codex-rs/tui/src/app/thread_settings.rs @@ -153,6 +153,8 @@ impl App { approvals_reviewer, permission_profile: _, active_permission_profile, + // TODO(anp): Support Windows sandbox updates through environment configuration; + // thread/settings/update cannot currently represent this override. windows_sandbox_level: _, model, effort, diff --git a/codex-rs/windows-sandbox-rs/src/elevated_impl.rs b/codex-rs/windows-sandbox-rs/src/elevated_impl.rs index 1de7889c1f..62f4d2a455 100644 --- a/codex-rs/windows-sandbox-rs/src/elevated_impl.rs +++ b/codex-rs/windows-sandbox-rs/src/elevated_impl.rs @@ -13,6 +13,8 @@ pub struct ElevatedSandboxProfileCaptureRequest<'a> { pub env_map: HashMap, pub timeout_ms: Option, pub cancellation: Option, + // TODO(anp): Reconcile this private-desktop copy with the supplied sandbox context + // (TurnEnvironment::sandbox_context for turns), preserving this launch snapshot. pub use_private_desktop: bool, pub proxy_enforced: bool, pub network_proxy_restricting_sid: Option, diff --git a/codex-rs/windows-sandbox-rs/src/unified_exec/mod.rs b/codex-rs/windows-sandbox-rs/src/unified_exec/mod.rs index 3e3599669d..4e6020a8f8 100644 --- a/codex-rs/windows-sandbox-rs/src/unified_exec/mod.rs +++ b/codex-rs/windows-sandbox-rs/src/unified_exec/mod.rs @@ -23,6 +23,8 @@ use std::path::PathBuf; /// /// Callers should parse their own input shape first, then use this request to /// share the elevated-vs-legacy backend selection and session launch path. +// TODO(anp): Reconcile Windows backend and desktop copies with the supplied sandbox +// context (TurnEnvironment::sandbox_context for turns), preserving this launch snapshot. pub struct WindowsSandboxSessionRequest<'a> { pub permission_profile: &'a PermissionProfile, pub workspace_roots: &'a [AbsolutePathBuf],