diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index f33f1c1ff4..292470e18f 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -2635,6 +2635,7 @@ dependencies = [ "codex-utils-cargo-bin", "codex-utils-cli", "codex-utils-path", + "codex-utils-path-uri", "codex-utils-pty", "codex-windows-sandbox", "crossterm", diff --git a/codex-rs/app-server/src/request_processors/command_exec_processor.rs b/codex-rs/app-server/src/request_processors/command_exec_processor.rs index 9ce19397d2..ad95c5be21 100644 --- a/codex-rs/app-server/src/request_processors/command_exec_processor.rs +++ b/codex-rs/app-server/src/request_processors/command_exec_processor.rs @@ -1,5 +1,6 @@ use super::*; use codex_core::exec_env::inject_apply_patch_env; +use codex_core::windows_sandbox::managed_proxy_routing_for_windows_sandbox; use codex_protocol::shell_environment::is_non_inheritable_env_var; #[derive(Clone)] @@ -262,6 +263,9 @@ impl CommandExecRequestProcessor { Some(spec) => match spec .start_proxy( &network_proxy_permission_profile, + managed_proxy_routing_for_windows_sandbox( + self.config.permissions.windows_sandbox_type, + ), /*policy_decider*/ None, /*blocked_request_observer*/ None, managed_network_requirements_enabled, diff --git a/codex-rs/cli/Cargo.toml b/codex-rs/cli/Cargo.toml index a03d851f35..8668aee897 100644 --- a/codex-rs/cli/Cargo.toml +++ b/codex-rs/cli/Cargo.toml @@ -73,6 +73,7 @@ codex-thread-store = { workspace = true } codex-tui = { workspace = true } codex-utils-absolute-path = { workspace = true } codex-utils-path = { workspace = true } +codex-utils-path-uri = { workspace = true } crossterm = { workspace = true, features = ["event-stream"] } futures = { workspace = true } http = { workspace = true } diff --git a/codex-rs/cli/src/debug_sandbox.rs b/codex-rs/cli/src/debug_sandbox.rs index b17f7aef30..aea39b94f2 100644 --- a/codex-rs/cli/src/debug_sandbox.rs +++ b/codex-rs/cli/src/debug_sandbox.rs @@ -19,6 +19,7 @@ use codex_core::exec_env::create_env; #[cfg(target_os = "macos")] use codex_core::spawn::CODEX_SANDBOX_ENV_VAR; use codex_core::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR; +use codex_core::windows_sandbox::managed_proxy_routing_for_windows_sandbox; use codex_protocol::config_types::SandboxMode; use codex_protocol::models::PermissionProfile; use codex_protocol::models::SandboxEnforcement; @@ -316,19 +317,22 @@ async fn run_command_under_sandbox( } } - // Special-case Windows sandbox: execute and exit the process to emulate inherited stdio. + // Legacy Windows sandbox sessions emulate inherited stdio and exit the process. if let SandboxType::Windows = sandbox_type { #[cfg(target_os = "windows")] { - run_command_under_windows_session( - &config, - &permission_profile, - command, - cwd, - workspace_roots, - env, - ) - .await; + if config.permissions.windows_sandbox_type != codex_sandboxing::SandboxType::WindowsMxc + { + run_command_under_windows_session( + &config, + &permission_profile, + command, + cwd, + workspace_roots, + env, + ) + .await; + } } #[cfg(not(target_os = "windows"))] { @@ -348,6 +352,7 @@ async fn run_command_under_sandbox( Some(spec) => Some( spec.start_proxy( &permission_profile, + managed_proxy_routing_for_windows_sandbox(config.permissions.windows_sandbox_type), /*policy_decider*/ None, /*blocked_request_observer*/ None, managed_network_requirements_enabled, @@ -363,7 +368,7 @@ async fn run_command_under_sandbox( .map(codex_core::config::StartedNetworkProxy::proxy); // Proxy containment depends on whether a proxy is active, not whether its // policy came from managed requirements. - #[cfg(target_os = "macos")] + #[cfg(any(target_os = "macos", target_os = "windows"))] let enforce_managed_network = network.is_some(); let managed_mitm_ca_trust_bundle_path = match network.as_ref() { Some(network) => network.managed_mitm_ca_trust_bundle_path(), @@ -449,9 +454,57 @@ async fn run_command_under_sandbox( ) .await? } + #[cfg(not(target_os = "windows"))] SandboxType::Windows => { unreachable!("Windows sandbox should have been handled above"); } + #[cfg(target_os = "windows")] + SandboxType::Windows => { + use codex_sandboxing::SandboxCommand; + use codex_sandboxing::SandboxManager; + use codex_sandboxing::SandboxTransformRequest; + use codex_utils_path_uri::PathUri; + + let (program, args) = command + .split_first() + .context("sandbox command must not be empty")?; + let sandbox_policy_cwd = PathUri::from_abs_path(&sandbox_policy_cwd); + let codex_self_exe = std::env::current_exe()?; + let request = SandboxManager::new().transform(SandboxTransformRequest { + command: SandboxCommand { + program: program.into(), + args: args.to_vec(), + cwd: PathUri::from_abs_path(&cwd), + env, + managed_network: None, + additional_permissions: None, + }, + permissions: &runtime_permission_profile, + sandbox: codex_sandboxing::SandboxType::WindowsMxc, + enforce_managed_network, + environment_id: None, + network: network.as_ref(), + sandbox_policy_cwd: &sandbox_policy_cwd, + sandbox_exe: Some(codex_self_exe.as_path()), + use_legacy_landlock: false, + windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Disabled, + windows_sandbox_private_desktop: false, + })?; + let (program, args) = request + .command + .split_first() + .context("missing MXC wrapper")?; + spawn_debug_sandbox_child( + PathBuf::from(program), + args.to_vec(), + /*arg0*/ None, + cwd.to_path_buf(), + runtime_permission_profile.network_sandbox_policy(), + request.env, + |_| {}, + ) + .await? + } }; #[cfg(target_os = "macos")] diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 38a832452d..869a992cd7 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -128,6 +128,7 @@ use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::MultiAgentVersion; use codex_protocol::protocol::SandboxPolicy; use codex_rmcp_client::McpOAuthRefreshMode; +use codex_sandboxing::SandboxType; pub use codex_thread_store::ExtraConfig; use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_absolute_path::AbsolutePathBufGuard; @@ -345,6 +346,8 @@ pub struct Permissions { /// Effective Windows sandbox mode derived from `[windows].sandbox` or /// legacy feature keys. pub windows_sandbox_mode: Option, + /// Selected Windows sandbox implementation, separate from the legacy setup level. + pub windows_sandbox_type: SandboxType, /// Whether the final Windows sandboxed child should run on a private desktop. pub windows_sandbox_private_desktop: bool, } @@ -367,6 +370,7 @@ impl Permissions { allow_login_shell: true, shell_environment_policy: ShellEnvironmentPolicy::default(), windows_sandbox_mode: None, + windows_sandbox_type: SandboxType::None, windows_sandbox_private_desktop: true, }) } @@ -3319,6 +3323,7 @@ impl Config { let enable_network_proxy = features.enabled(Feature::NetworkProxy); let PreparedWindowsSandboxConfig { mode: windows_sandbox_mode, + sandbox_type: windows_sandbox_type, level: windows_sandbox_level, } = prepare_windows_sandbox_config( resolve_windows_sandbox_mode(&cfg), @@ -4154,6 +4159,7 @@ impl Config { allow_login_shell, shell_environment_policy, windows_sandbox_mode, + windows_sandbox_type, windows_sandbox_private_desktop, }, explicit_permission_profile_mode, diff --git a/codex-rs/core/src/config/network_proxy_spec.rs b/codex-rs/core/src/config/network_proxy_spec.rs index d7eb787ff0..f29beb47e9 100644 --- a/codex-rs/core/src/config/network_proxy_spec.rs +++ b/codex-rs/core/src/config/network_proxy_spec.rs @@ -5,6 +5,7 @@ use codex_network_proxy::ConfigReloader; use codex_network_proxy::ConfigReloaderFuture; use codex_network_proxy::ConfigState; use codex_network_proxy::EnvironmentNetworkPolicy; +use codex_network_proxy::ManagedProxyRouting; use codex_network_proxy::NetworkDecision; use codex_network_proxy::NetworkPolicyDecider; use codex_network_proxy::NetworkProxy; @@ -34,13 +35,19 @@ pub struct NetworkProxySpec { pub struct StartedNetworkProxy { proxy: NetworkProxy, + network_policy_decider: Option>, _handle: NetworkProxyHandle, } impl StartedNetworkProxy { - fn new(proxy: NetworkProxy, handle: NetworkProxyHandle) -> Self { + fn new( + proxy: NetworkProxy, + handle: NetworkProxyHandle, + network_policy_decider: Option>, + ) -> Self { Self { proxy, + network_policy_decider, _handle: handle, } } @@ -48,6 +55,10 @@ impl StartedNetworkProxy { pub fn proxy(&self) -> NetworkProxy { self.proxy.clone() } + + pub(crate) fn network_policy_decider(&self) -> Option> { + self.network_policy_decider.as_ref().map(Arc::clone) + } } #[derive(Clone)] @@ -179,13 +190,17 @@ impl NetworkProxySpec { pub async fn start_proxy( &self, permission_profile: &PermissionProfile, + managed_proxy_routing: ManagedProxyRouting, policy_decider: Option>, blocked_request_observer: Option>, enable_network_approval_flow: bool, audit_metadata: NetworkProxyAuditMetadata, ) -> std::io::Result { let state = self.build_state_with_audit_metadata(audit_metadata)?; - let mut builder = NetworkProxy::builder().state(Arc::new(state)); + let mut builder = NetworkProxy::builder() + .state(Arc::new(state)) + .managed_proxy_routing(managed_proxy_routing); + let retained_policy_decider = policy_decider.as_ref().map(Arc::clone); if enable_network_approval_flow && !self.hard_deny_allowlist_misses { if let Some(policy_decider) = policy_decider { builder = builder.policy_decider_arc(policy_decider); @@ -204,7 +219,11 @@ impl NetworkProxySpec { .run() .await .map_err(|err| std::io::Error::other(format!("failed to run network proxy: {err}")))?; - Ok(StartedNetworkProxy::new(proxy, handle)) + Ok(StartedNetworkProxy::new( + proxy, + handle, + retained_policy_decider, + )) } pub(crate) fn recompute_for_permission_profile( diff --git a/codex-rs/core/src/config/windows_sandbox_config.rs b/codex-rs/core/src/config/windows_sandbox_config.rs index 66dae042db..940318ae88 100644 --- a/codex-rs/core/src/config/windows_sandbox_config.rs +++ b/codex-rs/core/src/config/windows_sandbox_config.rs @@ -5,11 +5,14 @@ use super::apply_requirement_constrained_value; use codex_config::ConstrainedWithSource; use codex_config::types::WindowsSandboxModeToml; use codex_protocol::config_types::WindowsSandboxLevel; +use codex_sandboxing::SandboxType; #[derive(Debug, PartialEq)] pub struct PreparedWindowsSandboxConfig { /// Explicit or requirement-constrained mode; excludes feature-only fallback. pub mode: Option, + /// Selected implementation, kept separate from the legacy setup level. + pub sandbox_type: SandboxType, /// Effective sandbox level after Windows requirements have been applied. pub level: WindowsSandboxLevel, } @@ -39,7 +42,16 @@ pub fn prepare_windows_sandbox_config( Some(WindowsSandboxModeToml::Unelevated) => WindowsSandboxLevel::RestrictedToken, None => WindowsSandboxLevel::Disabled, }; - Ok(PreparedWindowsSandboxConfig { mode, level }) + let sandbox_type = if level == WindowsSandboxLevel::Disabled { + SandboxType::None + } else { + SandboxType::WindowsRestrictedToken + }; + Ok(PreparedWindowsSandboxConfig { + mode, + sandbox_type, + level, + }) } #[cfg(test)] diff --git a/codex-rs/core/src/config/windows_sandbox_config_tests.rs b/codex-rs/core/src/config/windows_sandbox_config_tests.rs index c88425e934..2d4d70dac0 100644 --- a/codex-rs/core/src/config/windows_sandbox_config_tests.rs +++ b/codex-rs/core/src/config/windows_sandbox_config_tests.rs @@ -11,6 +11,7 @@ fn configured_mode_takes_priority_without_persisting_feature_fallback() -> std:: None, PreparedWindowsSandboxConfig { mode: None, + sandbox_type: SandboxType::WindowsRestrictedToken, level: WindowsSandboxLevel::Elevated, }, ), @@ -18,6 +19,7 @@ fn configured_mode_takes_priority_without_persisting_feature_fallback() -> std:: Some(WindowsSandboxModeToml::Unelevated), PreparedWindowsSandboxConfig { mode: Some(WindowsSandboxModeToml::Unelevated), + sandbox_type: SandboxType::WindowsRestrictedToken, level: WindowsSandboxLevel::RestrictedToken, }, ), diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 3272ecbf6e..65491108e5 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -163,6 +163,7 @@ use codex_rollout::state_db; use codex_rollout_trace::AgentResultTracePayload; use codex_rollout_trace::ThreadStartedTraceMetadata; use codex_rollout_trace::ThreadTraceContext; +use codex_sandboxing::SandboxType; use codex_sandboxing::policy_transforms::intersect_permission_profiles_with_context; use codex_shell_command::parse_command::parse_command; use codex_terminal_detection::user_agent; @@ -329,6 +330,7 @@ use crate::turn_timing::TurnTimingState; use crate::turn_timing::record_turn_ttfm_metric; use crate::unified_exec::UnifiedExecProcessManager; use crate::windows_sandbox::WindowsSandboxLevelExt; +use crate::windows_sandbox::managed_proxy_routing_for_windows_sandbox; use codex_core_plugins::PluginCommandAttribution; use codex_core_plugins::PluginsManager; use codex_core_plugins::RecommendedPluginCandidatesInput; @@ -836,6 +838,7 @@ 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_type: config.permissions.windows_sandbox_type, windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop, use_legacy_landlock: config.features.use_legacy_landlock(), legacy_fallback_cwd: config.cwd.clone(), @@ -1184,10 +1187,12 @@ impl Session { ) } + #[allow(clippy::too_many_arguments)] async fn start_managed_network_proxy( spec: &crate::config::NetworkProxySpec, exec_policy: &codex_execpolicy::Policy, permission_profile: &PermissionProfile, + windows_sandbox_type: SandboxType, network_policy_decider: Option>, blocked_request_observer: Option>, managed_network_requirements_enabled: bool, @@ -1205,6 +1210,7 @@ impl Session { let network_proxy = spec .start_proxy( permission_profile, + managed_proxy_routing_for_windows_sandbox(windows_sandbox_type), network_policy_decider, blocked_request_observer, managed_network_requirements_enabled, @@ -1273,7 +1279,15 @@ impl Session { .set_snapshot_credential_broker(SnapshotCredentialBrokerState::Inactive); return; } - if let Some(started_proxy) = self.services.network_proxy.load_full() { + let managed_proxy_routing = + managed_proxy_routing_for_windows_sandbox(session_configuration.windows_sandbox_type); + let started_proxy = self.services.network_proxy.load_full(); + let network_policy_decider = started_proxy + .as_ref() + .and_then(|started_proxy| started_proxy.network_policy_decider()); + if let Some(started_proxy) = started_proxy + && started_proxy.proxy().managed_proxy_routing() == managed_proxy_routing + { if let Err(err) = spec.apply_to_started_proxy(started_proxy.as_ref()).await { warn!("failed to refresh managed network proxy for sandbox change: {err}"); } else { @@ -1292,7 +1306,8 @@ impl Session { &spec, current_exec_policy.as_ref(), &session_configuration.permission_profile(), - /*network_policy_decider*/ None, + session_configuration.windows_sandbox_type, + network_policy_decider, Some(build_blocked_request_observer(Arc::clone( &self.services.network_approval, ))), diff --git a/codex-rs/core/src/session/session.rs b/codex-rs/core/src/session/session.rs index 259ef9ba83..7503f81984 100644 --- a/codex-rs/core/src/session/session.rs +++ b/codex-rs/core/src/session/session.rs @@ -40,6 +40,7 @@ use codex_protocol::protocol::MultiAgentVersion; use codex_protocol::protocol::ThreadHistoryMode; use codex_protocol::protocol::ThreadSource; use codex_protocol::protocol::TurnEnvironmentSelections; +use codex_sandboxing::SandboxType; use codex_skills::SkillError; use codex_utils_git_discovery::GitRootDiscovery; use codex_utils_path::replace_path_and_deduplicate; @@ -119,6 +120,7 @@ pub(crate) struct SessionConfiguration { // 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_type: SandboxType, pub(super) windows_sandbox_private_desktop: bool, pub(super) use_legacy_landlock: bool, @@ -1490,6 +1492,7 @@ impl Session { spec, current_exec_policy.as_ref(), config.permissions.permission_profile(), + config.permissions.windows_sandbox_type, network_policy_decider.as_ref().map(Arc::clone), blocked_request_observer.as_ref().map(Arc::clone), managed_network_requirements_configured, diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index 46a308d7cf..f20c4bb5e1 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -132,6 +132,8 @@ use codex_history::InitialHistory; use codex_history::ResponseItemEnvelope; use codex_history::ResumedHistory; use codex_history::RolloutItem; +#[cfg(windows)] +use codex_network_proxy::ManagedProxyRouting; use codex_network_proxy::NetworkProxyConfig; use codex_otel::MetricsClient; use codex_otel::MetricsConfig; @@ -1062,6 +1064,7 @@ async fn start_managed_network_proxy_applies_execpolicy_network_rules() -> anyho &spec, &exec_policy, &permission_profile, + SandboxType::None, /*network_policy_decider*/ None, /*blocked_request_observer*/ None, /*managed_network_requirements_enabled*/ false, @@ -1107,6 +1110,7 @@ async fn start_managed_network_proxy_ignores_invalid_execpolicy_network_rules() &spec, &exec_policy, &permission_profile, + SandboxType::None, /*network_policy_decider*/ None, /*blocked_request_observer*/ None, /*managed_network_requirements_enabled*/ false, @@ -1147,6 +1151,7 @@ async fn managed_network_proxy_decider_survives_full_access_start() -> anyhow::R &spec, &exec_policy, &full_access_permission_profile, + SandboxType::None, Some(network_policy_decider), /*blocked_request_observer*/ None, /*managed_network_requirements_enabled*/ true, @@ -1219,11 +1224,14 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow Some(requirements), &initial_permission_profile, )?; + let network_policy_decider: Arc = + Arc::new(|_request| async { codex_network_proxy::NetworkDecision::ask("not_allowed") }); let (started_proxy, _) = Session::start_managed_network_proxy( &spec, &Policy::empty(), &initial_permission_profile, - /*network_policy_decider*/ None, + SandboxType::None, + Some(Arc::clone(&network_policy_decider)), /*blocked_request_observer*/ None, /*managed_network_requirements_enabled*/ false, crate::config::NetworkProxyAuditMetadata::default(), @@ -1247,6 +1255,10 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow .session_configuration .set_permission_profile_for_tests(initial_permission_profile) .expect("test setup should allow permission profile"); + #[cfg(windows)] + { + state.session_configuration.windows_sandbox_type = SandboxType::WindowsMxc; + } } session .services @@ -1273,6 +1285,20 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow started_proxy.proxy().current_cfg().await?.allowed_domains(), Some(vec!["*.example.com".to_string()]) ); + #[cfg(windows)] + { + assert_eq!( + started_proxy.proxy().managed_proxy_routing(), + ManagedProxyRouting::DedicatedListeners + ); + let rebuilt_policy_decider = started_proxy + .network_policy_decider() + .expect("rebuilt managed network proxy should retain its policy decider"); + assert!(Arc::ptr_eq( + &network_policy_decider, + &rebuilt_policy_decider + )); + } Ok(()) } @@ -3945,6 +3971,7 @@ 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_type: config.permissions.windows_sandbox_type, windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop, use_legacy_landlock: config.features.use_legacy_landlock(), legacy_fallback_cwd: config.cwd.clone(), @@ -4066,6 +4093,7 @@ 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_type: config.permissions.windows_sandbox_type, windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop, use_legacy_landlock: config.features.use_legacy_landlock(), legacy_fallback_cwd: config.cwd.clone(), @@ -4677,6 +4705,7 @@ 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_type: config.permissions.windows_sandbox_type, windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop, use_legacy_landlock: config.features.use_legacy_landlock(), legacy_fallback_cwd: config.cwd.clone(), @@ -5761,6 +5790,7 @@ 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_type: config.permissions.windows_sandbox_type, windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop, use_legacy_landlock: config.features.use_legacy_landlock(), legacy_fallback_cwd: config.cwd.clone(), @@ -5941,6 +5971,7 @@ 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_type: config.permissions.windows_sandbox_type, windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop, use_legacy_landlock: config.features.use_legacy_landlock(), legacy_fallback_cwd: config.cwd.clone(), @@ -6248,6 +6279,7 @@ 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_type: config.permissions.windows_sandbox_type, windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop, use_legacy_landlock: config.features.use_legacy_landlock(), legacy_fallback_cwd: config.cwd.clone(), @@ -6379,6 +6411,7 @@ 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_type: config.permissions.windows_sandbox_type, windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop, use_legacy_landlock: config.features.use_legacy_landlock(), legacy_fallback_cwd: config.cwd.clone(), @@ -8120,6 +8153,7 @@ 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_type: config.permissions.windows_sandbox_type, windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop, use_legacy_landlock: config.features.use_legacy_landlock(), legacy_fallback_cwd: config.cwd.clone(), diff --git a/codex-rs/core/src/shell_snapshot_tests.rs b/codex-rs/core/src/shell_snapshot_tests.rs index d88af88829..4666327e0a 100644 --- a/codex-rs/core/src/shell_snapshot_tests.rs +++ b/codex-rs/core/src/shell_snapshot_tests.rs @@ -238,6 +238,7 @@ async fn inactive_profiles_keep_snapshots_but_active_brokers_require_sandbox() - let started_proxy = network_spec .start_proxy( &permission_profile, + codex_network_proxy::ManagedProxyRouting::SharedIngress, /*policy_decider*/ None, /*blocked_request_observer*/ None, /*enable_network_approval_flow*/ false, @@ -664,6 +665,7 @@ async fn snapshot_discovers_and_redacts_shell_initialized_credentials() -> Resul let started_proxy = network_spec .start_proxy( &permission_profile, + codex_network_proxy::ManagedProxyRouting::SharedIngress, /*policy_decider*/ None, /*blocked_request_observer*/ None, /*enable_network_approval_flow*/ false, diff --git a/codex-rs/core/src/windows_sandbox.rs b/codex-rs/core/src/windows_sandbox.rs index 72576c2916..d0573faa0f 100644 --- a/codex-rs/core/src/windows_sandbox.rs +++ b/codex-rs/core/src/windows_sandbox.rs @@ -9,6 +9,7 @@ use codex_login::default_client::originator; use codex_otel::sanitize_metric_tag_value; use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::PermissionProfile; +use codex_sandboxing::SandboxType; use codex_utils_absolute_path::AbsolutePathBuf; use std::collections::BTreeMap; use std::collections::HashMap; @@ -16,6 +17,16 @@ use std::path::Path; use std::path::PathBuf; use std::time::Instant; +pub fn managed_proxy_routing_for_windows_sandbox( + sandbox_type: SandboxType, +) -> codex_network_proxy::ManagedProxyRouting { + if cfg!(windows) && sandbox_type == SandboxType::WindowsMxc { + codex_network_proxy::ManagedProxyRouting::DedicatedListeners + } else { + codex_network_proxy::ManagedProxyRouting::SharedIngress + } +} + pub trait WindowsSandboxLevelExt { fn from_config(config: &Config) -> WindowsSandboxLevel; fn from_features(features: &Features) -> WindowsSandboxLevel; diff --git a/codex-rs/network-proxy/src/proxy.rs b/codex-rs/network-proxy/src/proxy.rs index ec0eaa6088..e8dc40864d 100644 --- a/codex-rs/network-proxy/src/proxy.rs +++ b/codex-rs/network-proxy/src/proxy.rs @@ -881,6 +881,10 @@ impl NetworkProxy { self.http_addr } + pub fn managed_proxy_routing(&self) -> ManagedProxyRouting { + self.managed_proxy_routing + } + pub fn socks_addr(&self) -> SocketAddr { #[cfg(target_os = "windows")] if let Some(runtime) = self.windows_runtime.as_ref() {