diff --git a/codex-rs/core/src/codex_delegate.rs b/codex-rs/core/src/codex_delegate.rs index 2c57444dc5..aa3a6d932f 100644 --- a/codex-rs/core/src/codex_delegate.rs +++ b/codex-rs/core/src/codex_delegate.rs @@ -87,6 +87,7 @@ pub(crate) async fn run_codex_thread_interactive( subagent_source: SubAgentSource, initial_history: Option, git_enrichment_policy: GitEnrichmentPolicy, + windows_sandbox_proxy_settings_mode: codex_sandboxing::WindowsSandboxProxySettingsMode, ) -> Result<(Arc, SessionIo), CodexErr> { let (tx_sub, rx_sub) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY); let (tx_ops, rx_ops) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY); @@ -139,6 +140,7 @@ pub(crate) async fn run_codex_thread_interactive( external_time_provider: Some(Arc::clone(&parent_session.services.time_provider)), inherited_multi_agent_version: Some(MultiAgentVersion::Disabled), git_enrichment_policy, + windows_sandbox_proxy_settings_mode, })) .or_cancel(&cancel_token) .await??; @@ -224,6 +226,7 @@ pub(crate) async fn run_codex_thread_one_shot( subagent_source, initial_history, GitEnrichmentPolicy::Fresh, + codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile, )) .await?; diff --git a/codex-rs/core/src/codex_delegate_tests.rs b/codex-rs/core/src/codex_delegate_tests.rs index 4a4d061f46..d1bebf5620 100644 --- a/codex-rs/core/src/codex_delegate_tests.rs +++ b/codex-rs/core/src/codex_delegate_tests.rs @@ -212,6 +212,7 @@ async fn run_codex_thread_interactive_respects_pre_cancelled_spawn() { SubAgentSource::Review, /*initial_history*/ None, crate::session::GitEnrichmentPolicy::Fresh, + codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile, ), ) .await diff --git a/codex-rs/core/src/guardian/review_session.rs b/codex-rs/core/src/guardian/review_session.rs index 61955ac084..0340043660 100644 --- a/codex-rs/core/src/guardian/review_session.rs +++ b/codex-rs/core/src/guardian/review_session.rs @@ -680,6 +680,7 @@ async fn spawn_guardian_review_session( SubAgentSource::Other(GUARDIAN_REVIEWER_NAME.to_string()), initial_history, GitEnrichmentPolicy::Skip, + codex_sandboxing::WindowsSandboxProxySettingsMode::Preserve, )) .await?; @@ -1254,6 +1255,31 @@ mod tests { } } + #[tokio::test] + async fn spawned_guardian_session_preserves_windows_sandbox_proxy_settings() { + let params = test_review_params().await; + let manager = GuardianReviewSessionManager::default(); + manager + .initialize(params.parent_session, params.parent_turn) + .await + .expect("initialize Guardian session"); + let mode = manager + .state + .lock() + .await + .trunk + .as_ref() + .expect("Guardian session") + .session + .windows_sandbox_proxy_settings_mode; + + assert_eq!( + mode, + codex_sandboxing::WindowsSandboxProxySettingsMode::Preserve + ); + manager.shutdown().await; + } + #[tokio::test] async fn guardian_review_session_config_change_invalidates_cached_session() { let parent_config = crate::config::test_config().await; diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 7f8a6ca546..5af8a34a3d 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -455,6 +455,8 @@ pub(crate) struct SessionSpawnArgs { pub(crate) external_time_provider: Option>, pub(crate) inherited_multi_agent_version: Option, pub(crate) git_enrichment_policy: GitEnrichmentPolicy, + pub(crate) windows_sandbox_proxy_settings_mode: + codex_sandboxing::WindowsSandboxProxySettingsMode, } pub(crate) fn resolve_multi_agent_version( @@ -544,6 +546,7 @@ impl Session { external_time_provider, inherited_multi_agent_version, git_enrichment_policy, + windows_sandbox_proxy_settings_mode, } = args; let (tx_sub, rx_sub) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY); let (tx_event, rx_event) = async_channel::unbounded(); @@ -732,6 +735,7 @@ impl Session { external_time_provider, multi_agent_version, git_enrichment_policy, + windows_sandbox_proxy_settings_mode, )) .await .map_err(|e| { diff --git a/codex-rs/core/src/session/session.rs b/codex-rs/core/src/session/session.rs index c7154ab122..41c24df898 100644 --- a/codex-rs/core/src/session/session.rs +++ b/codex-rs/core/src/session/session.rs @@ -37,6 +37,8 @@ pub(crate) struct Session { /// The set of enabled features should be invariant for the lifetime of the /// session. pub(super) features: ManagedFeatures, + pub(crate) windows_sandbox_proxy_settings_mode: + codex_sandboxing::WindowsSandboxProxySettingsMode, pub(super) multi_agent_version: OnceLock, pub(super) mcp_refresh_pending: std::sync::atomic::AtomicBool, /// Serializes runtime refreshes without blocking calls that own a snapshot. @@ -514,6 +516,7 @@ impl Session { external_time_provider: Option>, multi_agent_version: Option, git_enrichment_policy: GitEnrichmentPolicy, + windows_sandbox_proxy_settings_mode: codex_sandboxing::WindowsSandboxProxySettingsMode, ) -> anyhow::Result> { debug!( "Configuring session: model={}; provider={:?}", @@ -1150,6 +1153,7 @@ impl Session { state: Mutex::new(state), managed_network_proxy_refresh_lock: Semaphore::new(/*permits*/ 1), features: config.features.clone(), + windows_sandbox_proxy_settings_mode, multi_agent_version, mcp_refresh_pending: std::sync::atomic::AtomicBool::new(false), mcp_refresh_lock: Semaphore::new(/*permits*/ 1), diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index 0cc73231e6..0b284dd5a7 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -5288,6 +5288,7 @@ async fn session_new_fails_when_zsh_fork_enabled_without_packaged_zsh() { /*external_time_provider*/ None, Some(config.multi_agent_version_from_features()), GitEnrichmentPolicy::Fresh, + codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile, ) .await; @@ -5539,6 +5540,8 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) { state: Mutex::new(state), managed_network_proxy_refresh_lock: Semaphore::new(/*permits*/ 1), features: config.features.clone(), + windows_sandbox_proxy_settings_mode: + codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile, multi_agent_version: OnceLock::from(config.multi_agent_version_from_features()), mcp_refresh_pending: std::sync::atomic::AtomicBool::new(true), mcp_refresh_lock: Semaphore::new(/*permits*/ 1), @@ -5673,6 +5676,7 @@ async fn make_session_with_config_and_rx( /*external_time_provider*/ None, Some(config.multi_agent_version_from_features()), GitEnrichmentPolicy::Fresh, + codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile, ) .await?; @@ -5788,6 +5792,7 @@ async fn make_session_with_history_source_and_agent_control_and_rx( /*external_time_provider*/ None, Some(config.multi_agent_version_from_features()), GitEnrichmentPolicy::Fresh, + codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile, ) .await?; @@ -7702,6 +7707,8 @@ where state: Mutex::new(state), managed_network_proxy_refresh_lock: Semaphore::new(/*permits*/ 1), features: config.features.clone(), + windows_sandbox_proxy_settings_mode: + codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile, multi_agent_version: OnceLock::from(config.multi_agent_version_from_features()), mcp_refresh_pending: std::sync::atomic::AtomicBool::new(true), mcp_refresh_lock: Semaphore::new(/*permits*/ 1), diff --git a/codex-rs/core/src/session/tests/guardian_tests.rs b/codex-rs/core/src/session/tests/guardian_tests.rs index c09b53b353..2ce44e0e9b 100644 --- a/codex-rs/core/src/session/tests/guardian_tests.rs +++ b/codex-rs/core/src/session/tests/guardian_tests.rs @@ -766,6 +766,8 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() { external_time_provider: None, inherited_multi_agent_version: None, git_enrichment_policy: GitEnrichmentPolicy::Skip, + windows_sandbox_proxy_settings_mode: + codex_sandboxing::WindowsSandboxProxySettingsMode::Preserve, }) .await .expect("spawn guardian subagent"); diff --git a/codex-rs/core/src/session/turn_context.rs b/codex-rs/core/src/session/turn_context.rs index 8eb7e2a599..124842e8f5 100644 --- a/codex-rs/core/src/session/turn_context.rs +++ b/codex-rs/core/src/session/turn_context.rs @@ -360,6 +360,7 @@ impl TurnContext { .config .permissions .windows_sandbox_private_desktop, + windows_sandbox_proxy_settings_mode: None, use_legacy_landlock: self.config.features.use_legacy_landlock(), } } diff --git a/codex-rs/core/src/thread_manager.rs b/codex-rs/core/src/thread_manager.rs index 3a4b876dc3..a4f5bebc7d 100644 --- a/codex-rs/core/src/thread_manager.rs +++ b/codex-rs/core/src/thread_manager.rs @@ -1676,6 +1676,8 @@ impl ThreadManagerState { external_time_provider: self.external_time_provider.clone(), inherited_multi_agent_version: multi_agent_version, git_enrichment_policy: GitEnrichmentPolicy::Fresh, + windows_sandbox_proxy_settings_mode: + codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile, })) .await?; let new_thread = self diff --git a/codex-rs/core/src/tools/runtimes/apply_patch.rs b/codex-rs/core/src/tools/runtimes/apply_patch.rs index 2573f4f821..2d59a4de0a 100644 --- a/codex-rs/core/src/tools/runtimes/apply_patch.rs +++ b/codex-rs/core/src/tools/runtimes/apply_patch.rs @@ -99,6 +99,7 @@ impl ApplyPatchRuntime { workspace_roots: attempt.workspace_roots.to_vec(), windows_sandbox_level: attempt.windows_sandbox_level, windows_sandbox_private_desktop: attempt.windows_sandbox_private_desktop, + windows_sandbox_proxy_settings_mode: None, use_legacy_landlock: attempt.use_legacy_landlock, }) } diff --git a/codex-rs/core/src/tools/runtimes/unified_exec.rs b/codex-rs/core/src/tools/runtimes/unified_exec.rs index 9b0aeed52c..345a28a08d 100644 --- a/codex-rs/core/src/tools/runtimes/unified_exec.rs +++ b/codex-rs/core/src/tools/runtimes/unified_exec.rs @@ -302,6 +302,7 @@ impl<'a> ToolRuntime for UnifiedExecRunt ctx: &ToolCtx, ) -> Result { let base_command = &req.command; + let windows_sandbox_proxy_settings_mode = ctx.session.windows_sandbox_proxy_settings_mode; let session_shell = ctx.session.user_shell(); let shell = req .turn_environment @@ -464,6 +465,7 @@ impl<'a> ToolRuntime for UnifiedExecRunt .open_session_with_prepared_exec_env( req.process_id, &prepared.exec_request, + windows_sandbox_proxy_settings_mode, req.tty, prepared.spawn_lifecycle, req.turn_environment.environment.as_ref(), @@ -510,6 +512,7 @@ impl<'a> ToolRuntime for UnifiedExecRunt network_proxy_launch, /*environment_id*/ Some(&req.turn_environment.environment_id), req.exec_server_env_config.clone(), + windows_sandbox_proxy_settings_mode, req.tty, Box::new(NoopSpawnLifecycle), req.turn_environment.environment.as_ref(), diff --git a/codex-rs/core/src/tools/sandboxing.rs b/codex-rs/core/src/tools/sandboxing.rs index 56587a27d2..98332fd228 100644 --- a/codex-rs/core/src/tools/sandboxing.rs +++ b/codex-rs/core/src/tools/sandboxing.rs @@ -507,6 +507,7 @@ impl<'a> SandboxAttempt<'a> { workspace_roots: self.workspace_roots.to_vec(), windows_sandbox_level: self.windows_sandbox_level, windows_sandbox_private_desktop: self.windows_sandbox_private_desktop, + windows_sandbox_proxy_settings_mode: None, use_legacy_landlock: self.use_legacy_landlock, }); exec_request.exec_server_enforce_managed_network = self.enforce_managed_network; diff --git a/codex-rs/core/src/tools/sandboxing_tests.rs b/codex-rs/core/src/tools/sandboxing_tests.rs index e1b261b7d6..d7fed8ba64 100644 --- a/codex-rs/core/src/tools/sandboxing_tests.rs +++ b/codex-rs/core/src/tools/sandboxing_tests.rs @@ -268,6 +268,7 @@ fn exec_server_env_keeps_command_native_and_carries_sandbox_context() { workspace_roots: vec![cwd_uri.clone()], windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Disabled, windows_sandbox_private_desktop: false, + windows_sandbox_proxy_settings_mode: None, use_legacy_landlock: false, }) ); diff --git a/codex-rs/core/src/unified_exec/mod_tests.rs b/codex-rs/core/src/unified_exec/mod_tests.rs index 04cfa747ca..acf6d7d639 100644 --- a/codex-rs/core/src/unified_exec/mod_tests.rs +++ b/codex-rs/core/src/unified_exec/mod_tests.rs @@ -113,6 +113,7 @@ async fn exec_command_with_tty( .open_session_with_prepared_exec_env( process_id, &request, + codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile, tty, Box::new(NoopSpawnLifecycle), turn.environments @@ -753,6 +754,7 @@ async fn completed_pipe_commands_preserve_exit_code() -> anyhow::Result<()> { .open_session_with_prepared_exec_env( /*process_id*/ 1234, &request, + codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile, /*tty*/ false, Box::new(NoopSpawnLifecycle), &environment, @@ -793,6 +795,7 @@ async fn unified_exec_uses_remote_exec_server_when_configured() -> anyhow::Resul .open_session_with_prepared_exec_env( /*process_id*/ 1234, &request, + codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile, /*tty*/ true, Box::new(NoopSpawnLifecycle), remote_test_env.environment(), @@ -851,6 +854,7 @@ async fn remote_exec_server_rejects_inherited_fd_launches() -> anyhow::Result<() .open_session_with_prepared_exec_env( /*process_id*/ 1234, &request, + codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile, /*tty*/ true, Box::new(TestSpawnLifecycle { inherited_fds: vec![42], diff --git a/codex-rs/core/src/unified_exec/process_manager.rs b/codex-rs/core/src/unified_exec/process_manager.rs index dbd57378ea..098c70f026 100644 --- a/codex-rs/core/src/unified_exec/process_manager.rs +++ b/codex-rs/core/src/unified_exec/process_manager.rs @@ -177,9 +177,14 @@ fn exec_server_env_for_request( fn exec_server_params_for_request( process_id: i32, request: &ExecRequest, + windows_sandbox_proxy_settings_mode: codex_sandboxing::WindowsSandboxProxySettingsMode, tty: bool, ) -> codex_exec_server::ExecParams { let (env_policy, env) = exec_server_env_for_request(request); + let sandbox = request.exec_server_sandbox.clone().map(|mut sandbox| { + sandbox.windows_sandbox_proxy_settings_mode = Some(windows_sandbox_proxy_settings_mode); + sandbox + }); // Sandbox retries reuse the unified-exec ID but start a distinct executor process. let exec_server_process_id = if request.exec_server_sandbox.is_some() { format!("{process_id}-{}", Uuid::new_v4()) @@ -195,7 +200,7 @@ fn exec_server_params_for_request( tty, pipe_stdin: false, arg0: request.arg0.clone(), - sandbox: request.exec_server_sandbox.clone(), + sandbox, enforce_managed_network: request.exec_server_enforce_managed_network, managed_network: request.exec_server_managed_network.clone(), network_proxy: request.exec_server_network_proxy.clone(), @@ -1003,6 +1008,7 @@ impl UnifiedExecProcessManager { network_proxy_launch: Option, environment_id: Option<&str>, exec_server_env_config: Option, + windows_sandbox_proxy_settings_mode: codex_sandboxing::WindowsSandboxProxySettingsMode, tty: bool, spawn_lifecycle: SpawnLifecycleHandle, environment: &codex_exec_server::Environment, @@ -1018,6 +1024,7 @@ impl UnifiedExecProcessManager { self.open_session_with_prepared_exec_env( process_id, &request, + windows_sandbox_proxy_settings_mode, tty, spawn_lifecycle, environment, @@ -1038,6 +1045,7 @@ impl UnifiedExecProcessManager { &self, process_id: i32, request: &ExecRequest, + windows_sandbox_proxy_settings_mode: codex_sandboxing::WindowsSandboxProxySettingsMode, tty: bool, mut spawn_lifecycle: SpawnLifecycleHandle, environment: &codex_exec_server::Environment, @@ -1053,7 +1061,12 @@ impl UnifiedExecProcessManager { let started = environment .get_exec_backend() - .start(exec_server_params_for_request(process_id, request, tty)) + .start(exec_server_params_for_request( + process_id, + request, + windows_sandbox_proxy_settings_mode, + tty, + )) .await .map_err(|err| UnifiedExecError::create_process(err.to_string()))?; spawn_lifecycle.after_spawn(); @@ -1100,22 +1113,21 @@ impl UnifiedExecProcessManager { None:: } }; - let windows_sandbox = if request.sandbox - == codex_sandboxing::SandboxType::WindowsRestrictedToken - { - Some(codex_sandboxing::WindowsSandboxSpawnRequest { - permission_profile: &request.permission_profile, - workspace_roots: &request.windows_sandbox_workspace_roots, - windows_sandbox_level: request.windows_sandbox_level, - proxy_enforced: request.network.is_some(), - network_proxy_restricting_sid: network_proxy_restricting_sid.as_deref(), - proxy_settings_mode: codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile, - filesystem_overrides: request.windows_sandbox_filesystem_overrides.as_ref(), - use_private_desktop: request.windows_sandbox_private_desktop, - }) - } else { - None - }; + let windows_sandbox = + if request.sandbox == codex_sandboxing::SandboxType::WindowsRestrictedToken { + Some(codex_sandboxing::WindowsSandboxSpawnRequest { + permission_profile: &request.permission_profile, + workspace_roots: &request.windows_sandbox_workspace_roots, + windows_sandbox_level: request.windows_sandbox_level, + proxy_enforced: request.network.is_some(), + network_proxy_restricting_sid: network_proxy_restricting_sid.as_deref(), + proxy_settings_mode: windows_sandbox_proxy_settings_mode, + filesystem_overrides: request.windows_sandbox_filesystem_overrides.as_ref(), + use_private_desktop: request.windows_sandbox_private_desktop, + }) + } else { + None + }; let spawn_result = codex_sandboxing::spawn_process(codex_sandboxing::SpawnRequest { command: &request.command, cwd: native_cwd.as_path(), diff --git a/codex-rs/core/src/unified_exec/process_manager_tests.rs b/codex-rs/core/src/unified_exec/process_manager_tests.rs index bd094ab3d7..ced51882a1 100644 --- a/codex-rs/core/src/unified_exec/process_manager_tests.rs +++ b/codex-rs/core/src/unified_exec/process_manager_tests.rs @@ -177,8 +177,16 @@ fn exec_server_params_use_path_uri_and_env_policy_overlay_contract() { exec_server_network_proxy: None, }; - let params = - exec_server_params_for_request(/*process_id*/ 123, &request, /*tty*/ true); + let proxy_settings_mode = codex_sandboxing::WindowsSandboxProxySettingsMode::Preserve; + let params_for_request = |request: &ExecRequest| { + exec_server_params_for_request( + /*process_id*/ 123, + request, + proxy_settings_mode, + /*tty*/ true, + ) + }; + let params = params_for_request(&request); assert_eq!(params.process_id.as_str(), "123"); assert_eq!(params.cwd, request.cwd); @@ -200,10 +208,15 @@ fn exec_server_params_use_path_uri_and_env_policy_overlay_contract() { request.exec_server_sandbox = Some( codex_exec_server::FileSystemSandboxContext::from_permission_profile(permission_profile), ); - let first = - exec_server_params_for_request(/*process_id*/ 123, &request, /*tty*/ true); - let second = - exec_server_params_for_request(/*process_id*/ 123, &request, /*tty*/ true); + let first = params_for_request(&request); + let second = params_for_request(&request); + assert_eq!( + first + .sandbox + .as_ref() + .and_then(|sandbox| sandbox.windows_sandbox_proxy_settings_mode), + Some(codex_sandboxing::WindowsSandboxProxySettingsMode::Preserve) + ); assert!(first.process_id.as_str().starts_with("123-")); assert!(second.process_id.as_str().starts_with("123-")); assert_ne!(first.process_id, second.process_id); diff --git a/codex-rs/exec-server-protocol/src/protocol.rs b/codex-rs/exec-server-protocol/src/protocol.rs index 0468c413d5..b87b261d73 100644 --- a/codex-rs/exec-server-protocol/src/protocol.rs +++ b/codex-rs/exec-server-protocol/src/protocol.rs @@ -743,6 +743,7 @@ mod tests { use codex_network_proxy::NetworkProxyConfig; use codex_network_proxy::RemoteNetworkProxyConfig; use codex_network_proxy::RemoteNetworkProxyLaunchConfig; + use codex_protocol::config_types::WindowsSandboxProxySettingsMode; use codex_protocol::models::ManagedFileSystemPermissions; use codex_protocol::models::PermissionProfile; use codex_protocol::permissions::FileSystemAccessMode; @@ -938,6 +939,17 @@ mod tests { .expect("deserialize sandbox"), sandbox ); + let preserve = FileSystemSandboxContext { + windows_sandbox_proxy_settings_mode: Some(WindowsSandboxProxySettingsMode::Preserve), + ..sandbox + }; + let serialized = serde_json::to_value(&preserve).expect("serialize preserve mode"); + assert_eq!(serialized["windowsSandboxProxySettingsMode"], "preserve"); + assert_eq!( + serde_json::from_value::(serialized) + .expect("deserialize preserve mode"), + preserve + ); } #[test] diff --git a/codex-rs/exec-server/src/process_sandbox.rs b/codex-rs/exec-server/src/process_sandbox.rs index 864592c1a2..0c8327f585 100644 --- a/codex-rs/exec-server/src/process_sandbox.rs +++ b/codex-rs/exec-server/src/process_sandbox.rs @@ -52,6 +52,7 @@ struct PreparedWindowsSandboxRequest { windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel, proxy_enforced: bool, network_proxy_restricting_sid: Option, + proxy_settings_mode: WindowsSandboxProxySettingsMode, filesystem_overrides: Option, use_private_desktop: bool, } @@ -66,7 +67,7 @@ impl PreparedExecRequest { windows_sandbox_level: request.windows_sandbox_level, proxy_enforced: request.proxy_enforced, network_proxy_restricting_sid: request.network_proxy_restricting_sid.as_deref(), - proxy_settings_mode: WindowsSandboxProxySettingsMode::Reconcile, + proxy_settings_mode: request.proxy_settings_mode, filesystem_overrides: request.filesystem_overrides.as_ref(), use_private_desktop: request.use_private_desktop, }) @@ -114,6 +115,9 @@ pub(crate) async fn prepare_exec_request( windows_sandbox: None, }); }; + let windows_sandbox_proxy_settings_mode = sandbox_context + .windows_sandbox_proxy_settings_mode + .unwrap_or_default(); let runtime_paths = runtime_paths .ok_or_else(|| invalid_params("sandbox runtime paths are not configured".to_string()))?; // TODO(jif): Transport permissions before orchestrator-local paths are materialized, @@ -212,8 +216,7 @@ pub(crate) async fn prepare_exec_request( let (program, args) = (program.into(), args.to_vec()); let transform_request = SandboxDirectSpawnTransformRequest { workspace_roots, - windows_sandbox_proxy_settings_mode: - codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile, + windows_sandbox_proxy_settings_mode, transform: SandboxTransformRequest { command: SandboxCommand { program, @@ -271,6 +274,7 @@ pub(crate) async fn prepare_exec_request( windows_sandbox_level: sandbox_context.windows_sandbox_level, proxy_enforced, network_proxy_restricting_sid, + proxy_settings_mode: windows_sandbox_proxy_settings_mode, filesystem_overrides, use_private_desktop: sandbox_context.windows_sandbox_private_desktop, }) diff --git a/codex-rs/exec-server/src/process_sandbox_tests.rs b/codex-rs/exec-server/src/process_sandbox_tests.rs index 23ce7871f2..3ea7e508d3 100644 --- a/codex-rs/exec-server/src/process_sandbox_tests.rs +++ b/codex-rs/exec-server/src/process_sandbox_tests.rs @@ -394,6 +394,8 @@ async fn managed_network_selects_elevated_windows_spawn() { cwd_uri.clone(), ); sandbox.windows_sandbox_level = WindowsSandboxLevel::RestrictedToken; + sandbox.windows_sandbox_proxy_settings_mode = + Some(codex_sandboxing::WindowsSandboxProxySettingsMode::Preserve); let proxy_config = RemoteNetworkProxyConfig::from_effective_config(&NetworkProxyConfig { enabled: true, enable_socks5: false, @@ -434,6 +436,10 @@ async fn managed_network_selects_elevated_windows_spawn() { ); assert!(spawn.proxy_enforced); assert!(spawn.network_proxy_restricting_sid.is_some()); + assert_eq!( + spawn.proxy_settings_mode, + codex_sandboxing::WindowsSandboxProxySettingsMode::Preserve + ); assert_eq!(spawn.permission_profile, &permissions); assert_eq!(spawn.workspace_roots, std::slice::from_ref(&cwd)); } diff --git a/codex-rs/file-system/src/lib.rs b/codex-rs/file-system/src/lib.rs index 21f1cc6e37..fa783074fc 100644 --- a/codex-rs/file-system/src/lib.rs +++ b/codex-rs/file-system/src/lib.rs @@ -2,6 +2,7 @@ mod find_up; use bytes::Bytes; use codex_protocol::config_types::WindowsSandboxLevel; +use codex_protocol::config_types::WindowsSandboxProxySettingsMode; use codex_protocol::models::ManagedFileSystemPermissions; use codex_protocol::models::PermissionProfile; use codex_protocol::models::SandboxEnforcement; @@ -291,6 +292,8 @@ pub struct FileSystemSandboxContext { pub windows_sandbox_level: WindowsSandboxLevel, #[serde(default)] pub windows_sandbox_private_desktop: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub windows_sandbox_proxy_settings_mode: Option, #[serde(default)] pub use_legacy_landlock: bool, } @@ -332,6 +335,7 @@ impl FileSystemSandboxContext { workspace_roots, windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_private_desktop: false, + windows_sandbox_proxy_settings_mode: None, use_legacy_landlock: false, } } diff --git a/codex-rs/protocol/src/config_types.rs b/codex-rs/protocol/src/config_types.rs index c00b69943e..c29ca9488a 100644 --- a/codex-rs/protocol/src/config_types.rs +++ b/codex-rs/protocol/src/config_types.rs @@ -283,6 +283,16 @@ pub enum WindowsSandboxLevel { Elevated, } +/// Controls whether a Windows sandbox launch reconciles persistent proxy settings or preserves +/// the settings established by another launch. +#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum WindowsSandboxProxySettingsMode { + #[default] + Reconcile, + Preserve, +} + #[derive( Debug, Serialize, diff --git a/codex-rs/windows-sandbox-rs/src/identity.rs b/codex-rs/windows-sandbox-rs/src/identity.rs index 9ac3402be1..860877dff2 100644 --- a/codex-rs/windows-sandbox-rs/src/identity.rs +++ b/codex-rs/windows-sandbox-rs/src/identity.rs @@ -371,4 +371,36 @@ mod tests { vec![8080] ); } + + #[test] + fn guardian_preserve_mode_does_not_churn_marker_with_empty_proxy_ports() { + let marker = SetupMarker { + version: crate::setup::SETUP_VERSION, + offline_username: "offline".to_string(), + online_username: "online".to_string(), + created_at: None, + proxy_ports: vec![3128, 8081], + allow_local_binding: true, + }; + let env_map = HashMap::new(); + let reconciled = desired_offline_proxy_settings( + Some(&marker), + WindowsSandboxProxySettingsMode::Reconcile, + &env_map, + SandboxNetworkIdentity::Offline, + ); + assert_eq!(reconciled.proxy_ports, Vec::::new()); + + let desired = desired_offline_proxy_settings( + Some(&marker), + WindowsSandboxProxySettingsMode::Preserve, + &env_map, + SandboxNetworkIdentity::Offline, + ); + assert_eq!(desired, marker.offline_proxy_settings()); + assert_eq!( + marker.request_mismatch_reason(SandboxNetworkIdentity::Offline, &desired), + None + ); + } } diff --git a/codex-rs/windows-sandbox-rs/src/lib.rs b/codex-rs/windows-sandbox-rs/src/lib.rs index d4db186b9c..f86f1741ce 100644 --- a/codex-rs/windows-sandbox-rs/src/lib.rs +++ b/codex-rs/windows-sandbox-rs/src/lib.rs @@ -35,14 +35,7 @@ impl fmt::Debug for WindowsSandboxCancellationToken { } } -/// Controls whether a Windows sandbox launch reconciles persistent proxy -/// firewall settings or preserves the settings established by another launch. -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -pub enum WindowsSandboxProxySettingsMode { - #[default] - Reconcile, - Preserve, -} +pub use codex_protocol::config_types::WindowsSandboxProxySettingsMode; /// Network settings installed by an administrator during managed Windows sandbox setup. #[derive(Clone, Debug, Default, PartialEq, Eq)]