diff --git a/codex-rs/app-server/src/request_processors/thread_processor.rs b/codex-rs/app-server/src/request_processors/thread_processor.rs index 1f8b593841..04c2229652 100644 --- a/codex-rs/app-server/src/request_processors/thread_processor.rs +++ b/codex-rs/app-server/src/request_processors/thread_processor.rs @@ -4931,7 +4931,7 @@ impl ThreadRequestProcessor { serde_json::json!("unelevated"), ); } - WindowsSandboxLevel::Disabled | WindowsSandboxLevel::Mxc => {} + WindowsSandboxLevel::Disabled => {} } } let request_overrides = if cli_overrides.is_empty() { diff --git a/codex-rs/app-server/src/request_processors/windows_sandbox_processor.rs b/codex-rs/app-server/src/request_processors/windows_sandbox_processor.rs index 41a6233d0a..3c45b33125 100644 --- a/codex-rs/app-server/src/request_processors/windows_sandbox_processor.rs +++ b/codex-rs/app-server/src/request_processors/windows_sandbox_processor.rs @@ -303,9 +303,7 @@ fn determine_windows_sandbox_readiness_from_state( ) -> WindowsSandboxReadinessResponse { let status = match windows_sandbox_level { WindowsSandboxLevel::Disabled => WindowsSandboxReadiness::NotConfigured, - WindowsSandboxLevel::RestrictedToken | WindowsSandboxLevel::Mxc => { - WindowsSandboxReadiness::Ready - } + WindowsSandboxLevel::RestrictedToken => WindowsSandboxReadiness::Ready, WindowsSandboxLevel::Elevated => { if sandbox_setup_is_complete { WindowsSandboxReadiness::Ready diff --git a/codex-rs/core/src/config/windows_sandbox_config.rs b/codex-rs/core/src/config/windows_sandbox_config.rs index e50695c323..66dae042db 100644 --- a/codex-rs/core/src/config/windows_sandbox_config.rs +++ b/codex-rs/core/src/config/windows_sandbox_config.rs @@ -25,7 +25,7 @@ pub fn prepare_windows_sandbox_config( let selected_mode = configured_mode.or(match feature_level { WindowsSandboxLevel::Elevated => Some(WindowsSandboxModeToml::Elevated), WindowsSandboxLevel::RestrictedToken => Some(WindowsSandboxModeToml::Unelevated), - WindowsSandboxLevel::Disabled | WindowsSandboxLevel::Mxc => None, + WindowsSandboxLevel::Disabled => None, }); apply_requirement_constrained_value("windows.sandbox", selected_mode, constraint, warnings)?; let effective_mode = *constraint.get(); diff --git a/codex-rs/core/src/sandbox_tags.rs b/codex-rs/core/src/sandbox_tags.rs index 120dbff0da..9d94922768 100644 --- a/codex-rs/core/src/sandbox_tags.rs +++ b/codex-rs/core/src/sandbox_tags.rs @@ -79,7 +79,6 @@ fn permission_profile_sandbox_tag( } if cfg!(target_os = "windows") { match windows_sandbox_level { - WindowsSandboxLevel::Mxc => return "windows_mxc", WindowsSandboxLevel::Elevated => return "windows_elevated", WindowsSandboxLevel::Disabled | WindowsSandboxLevel::RestrictedToken => {} } diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index e70d45a9b7..46a308d7cf 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -8970,7 +8970,11 @@ async fn capability_discovery_uses_environment_permission_profile() { workspace_roots: environment.workspace_roots().to_vec(), user_home_dir: environment.user_home_dir.clone(), temporary_directories: environment.temporary_directories.clone(), - windows_sandbox_level: WindowsSandboxLevel::Elevated, + windows_sandbox_selection: if cfg!(windows) { + codex_file_system::WindowsSandboxSelection::Elevated + } else { + codex_file_system::WindowsSandboxSelection::Disabled + }, windows_sandbox_private_desktop: false, windows_sandbox_proxy_settings_mode: None, use_legacy_landlock: true, diff --git a/codex-rs/core/src/session/turn_context.rs b/codex-rs/core/src/session/turn_context.rs index 9f86627a45..d8a6c34fc7 100644 --- a/codex-rs/core/src/session/turn_context.rs +++ b/codex-rs/core/src/session/turn_context.rs @@ -9,7 +9,7 @@ use crate::exec_policy::AllowPrefixRules; use crate::shell_snapshot::ShellSnapshot; use crate::shell_snapshot::ShellSnapshotFile; use crate::shell_snapshot::ShellSnapshotSandbox; -use crate::tools::sandboxing::executor_windows_sandbox_level; +use crate::tools::sandboxing::executor_windows_sandbox_selection; use arc_swap::ArcSwap; use codex_core_plugins::PluginCommandAttribution; use codex_core_plugins::ResolvedPluginMetricsOperation; @@ -227,7 +227,7 @@ impl TurnEnvironment { workspace_roots: self.workspace_roots().to_vec(), user_home_dir: self.user_home_dir.clone(), temporary_directories: self.temporary_directories.clone(), - windows_sandbox_level: executor_windows_sandbox_level( + windows_sandbox_selection: executor_windows_sandbox_selection( config.windows_sandbox_level, self.cwd(), ), diff --git a/codex-rs/core/src/tools/runtimes/apply_patch.rs b/codex-rs/core/src/tools/runtimes/apply_patch.rs index 723e6eb034..435883b4b1 100644 --- a/codex-rs/core/src/tools/runtimes/apply_patch.rs +++ b/codex-rs/core/src/tools/runtimes/apply_patch.rs @@ -13,7 +13,7 @@ use crate::tools::sandboxing::Sandboxable; use crate::tools::sandboxing::ToolCtx; use crate::tools::sandboxing::ToolError; use crate::tools::sandboxing::ToolRuntime; -use crate::tools::sandboxing::executor_windows_sandbox_level; +use crate::tools::sandboxing::executor_windows_sandbox_selection; use codex_apply_patch::AppliedPatchDelta; use codex_apply_patch::ApplyPatchAction; use codex_apply_patch::ApplyPatchOptions; @@ -102,7 +102,7 @@ impl ApplyPatchRuntime { workspace_roots: attempt.workspace_roots.to_vec(), user_home_dir: req.turn_environment.user_home_dir.clone(), temporary_directories: None, - windows_sandbox_level: executor_windows_sandbox_level( + windows_sandbox_selection: executor_windows_sandbox_selection( attempt.windows_sandbox_level, attempt.sandbox_cwd, ), 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 becb1c4839..85a76226d5 100644 --- a/codex-rs/core/src/tools/runtimes/apply_patch_tests.rs +++ b/codex-rs/core/src/tools/runtimes/apply_patch_tests.rs @@ -276,8 +276,12 @@ async fn file_system_sandbox_context_preserves_executor_workspace_permissions() Some(codex_utils_path_uri::PathUri::from_abs_path(&path)) ); assert_eq!( - sandbox.windows_sandbox_level, - WindowsSandboxLevel::RestrictedToken + sandbox.windows_sandbox_selection, + if cfg!(windows) { + codex_file_system::WindowsSandboxSelection::RestrictedToken + } else { + codex_file_system::WindowsSandboxSelection::Disabled + } ); assert_eq!(sandbox.windows_sandbox_private_desktop, true); assert_eq!(sandbox.use_legacy_landlock, true); @@ -349,7 +353,7 @@ async fn file_system_sandbox_context_respects_sandbox_request() { workspace_roots: vec![cwd], user_home_dir: Some(user_home_dir), temporary_directories: None, - windows_sandbox_level: WindowsSandboxLevel::RestrictedToken, + windows_sandbox_selection: codex_file_system::WindowsSandboxSelection::RestrictedToken, windows_sandbox_private_desktop: false, windows_sandbox_proxy_settings_mode: None, use_legacy_landlock: false, diff --git a/codex-rs/core/src/tools/sandboxing.rs b/codex-rs/core/src/tools/sandboxing.rs index a9931e54e0..1bcce42afd 100644 --- a/codex-rs/core/src/tools/sandboxing.rs +++ b/codex-rs/core/src/tools/sandboxing.rs @@ -13,6 +13,7 @@ use crate::state::SessionServices; use crate::tools::hook_names::HookToolName; use crate::tools::network_approval::NetworkApprovalSpec; use codex_file_system::FileSystemSandboxContext; +use codex_file_system::WindowsSandboxSelection; use codex_network_proxy::NetworkProxy; use codex_protocol::approvals::ExecPolicyAmendment; use codex_protocol::config_types::WindowsSandboxLevel; @@ -417,6 +418,17 @@ pub(crate) fn executor_windows_sandbox_level( } } +pub(crate) fn executor_windows_sandbox_selection( + windows_sandbox_level: WindowsSandboxLevel, + cwd: &PathUri, +) -> WindowsSandboxSelection { + if cwd.infer_path_convention() == Some(PathConvention::Windows) { + executor_windows_sandbox_level(windows_sandbox_level, cwd).into() + } else { + WindowsSandboxSelection::Disabled + } +} + impl<'a> SandboxAttempt<'a> { /// Whether this attempt bypasses sandboxing required by its ambient policy. /// Use the requested policy, not the controller's wrapper, for remote executors. @@ -513,7 +525,7 @@ impl<'a> SandboxAttempt<'a> { workspace_roots: self.workspace_roots.to_vec(), user_home_dir: None, temporary_directories: None, - windows_sandbox_level: executor_windows_sandbox_level( + windows_sandbox_selection: executor_windows_sandbox_selection( self.windows_sandbox_level, self.sandbox_cwd, ), diff --git a/codex-rs/core/src/tools/sandboxing_tests.rs b/codex-rs/core/src/tools/sandboxing_tests.rs index f126685631..ee146a1ebb 100644 --- a/codex-rs/core/src/tools/sandboxing_tests.rs +++ b/codex-rs/core/src/tools/sandboxing_tests.rs @@ -373,10 +373,10 @@ fn exec_server_env_keeps_command_native_and_carries_sandbox_context() { workspace_roots: vec![cwd_uri.clone()], user_home_dir: None, temporary_directories: None, - windows_sandbox_level: if cfg!(windows) { - codex_protocol::config_types::WindowsSandboxLevel::RestrictedToken + windows_sandbox_selection: if cfg!(windows) { + codex_file_system::WindowsSandboxSelection::RestrictedToken } else { - codex_protocol::config_types::WindowsSandboxLevel::Disabled + codex_file_system::WindowsSandboxSelection::Disabled }, windows_sandbox_private_desktop: false, windows_sandbox_proxy_settings_mode: None, diff --git a/codex-rs/core/src/unified_exec/shell_snapshot.rs b/codex-rs/core/src/unified_exec/shell_snapshot.rs index c04b7d541e..941266f5d2 100644 --- a/codex-rs/core/src/unified_exec/shell_snapshot.rs +++ b/codex-rs/core/src/unified_exec/shell_snapshot.rs @@ -27,7 +27,7 @@ use crate::exec_env::inject_session_env; use crate::session::session::Session; use crate::session::step_context::StepContext; use crate::shell::ShellType; -use crate::tools::sandboxing::executor_windows_sandbox_level; +use crate::tools::sandboxing::executor_windows_sandbox_selection; impl Session { pub(crate) fn prewarm_shell_snapshots( @@ -97,7 +97,7 @@ impl Session { workspace_roots: environment.workspace_roots().to_vec(), user_home_dir: None, temporary_directories: None, - windows_sandbox_level: executor_windows_sandbox_level( + windows_sandbox_selection: executor_windows_sandbox_selection( environment.config().windows_sandbox_level, environment.cwd(), ), diff --git a/codex-rs/core/src/unified_exec/stdin_approval.rs b/codex-rs/core/src/unified_exec/stdin_approval.rs index aff87065cd..750fd3f954 100644 --- a/codex-rs/core/src/unified_exec/stdin_approval.rs +++ b/codex-rs/core/src/unified_exec/stdin_approval.rs @@ -1,7 +1,7 @@ //! Reviews terminal input against retained launch permissions and current policy. //! Permission snapshots stay host-owned; neither an earlier approval nor a policy //! change alters the sandbox of an already-running process. Native launches keep -//! their configured Windows sandbox level; executor launches use executor defaults. +//! their configured Windows sandbox selection; executor launches use executor defaults. use super::ProcessEntry; use super::UnifiedExecContext; @@ -56,7 +56,7 @@ impl TerminalPolicy { if matches!(sandbox_source, TerminalSandboxSource::Native) { // The filesystem helper applies executor defaults, but native process // launches honor Disabled. Preserve it so later enablement is detected. - sandbox.windows_sandbox_level = environment.config().windows_sandbox_level; + sandbox.windows_sandbox_selection = environment.config().windows_sandbox_level.into(); } Self { sandbox, diff --git a/codex-rs/exec-server-protocol/src/protocol.rs b/codex-rs/exec-server-protocol/src/protocol.rs index bf6ea86335..67125ab0c2 100644 --- a/codex-rs/exec-server-protocol/src/protocol.rs +++ b/codex-rs/exec-server-protocol/src/protocol.rs @@ -920,6 +920,7 @@ mod tests { use super::ProcessSandboxType; use super::ShellInfo; use codex_file_system::FileSystemSandboxContext; + use codex_file_system::WindowsSandboxSelection; use codex_network_proxy::ManagedNetworkSandboxContext; use codex_network_proxy::NetworkProxyAuditMetadata; use codex_network_proxy::NetworkProxyConfig; @@ -1274,9 +1275,11 @@ mod tests { let mut sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(permissions, cwd.clone()); sandbox.user_home_dir = Some(cwd.clone()); + sandbox.windows_sandbox_selection = WindowsSandboxSelection::Mxc; let serialized = serde_json::to_value(&sandbox).expect("serialize sandbox"); + assert_eq!(serialized["windowsSandboxLevel"], "mxc"); assert_eq!( serialized["userHomeDir"], serde_json::json!(cwd.to_string()) diff --git a/codex-rs/exec-server/src/capability_discovery.rs b/codex-rs/exec-server/src/capability_discovery.rs index 43721630e2..c1c5f44883 100644 --- a/codex-rs/exec-server/src/capability_discovery.rs +++ b/codex-rs/exec-server/src/capability_discovery.rs @@ -78,9 +78,7 @@ async fn discover_root( #[cfg(target_os = "windows")] if sandbox.is_some_and(|context| { - context.should_run_in_sandbox() - && context.windows_sandbox_level - == codex_protocol::config_types::WindowsSandboxLevel::Disabled + context.should_run_in_sandbox() && !context.windows_sandbox_is_requested() }) { discovery.error = Some("filesystem sandbox is unavailable on this executor".to_string()); return discovery; diff --git a/codex-rs/exec-server/src/fs_sandbox.rs b/codex-rs/exec-server/src/fs_sandbox.rs index 566973e526..74a20d1a5c 100644 --- a/codex-rs/exec-server/src/fs_sandbox.rs +++ b/codex-rs/exec-server/src/fs_sandbox.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use std::time::Duration; use codex_exec_server_protocol::JSONRPCErrorError; +use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::PermissionProfile; use codex_protocol::permissions::FileSystemAccessMode; use codex_protocol::permissions::FileSystemPath; @@ -16,7 +17,6 @@ use codex_sandboxing::SandboxExecRequest; use codex_sandboxing::SandboxManager; use codex_sandboxing::SandboxTransformRequest; use codex_sandboxing::SandboxType; -use codex_sandboxing::SandboxablePreference; use codex_utils_absolute_path::AbsolutePathBuf; #[cfg(not(target_os = "linux"))] use codex_utils_absolute_path::canonicalize_preserving_symlinks; @@ -144,10 +144,10 @@ impl FileSystemSandboxRunner { let sandbox_manager = sandbox_manager.with_allowed_symlinked_codex_home( self.runtime_paths.allowed_symlinked_codex_home.clone(), ); - let sandbox = sandbox_manager.select_initial( + let (sandbox, windows_sandbox_level) = crate::sandbox_selection::select_sandbox( + &sandbox_manager, permission_profile, - SandboxablePreference::Require, - sandbox_context.windows_sandbox_level, + sandbox_context, /*has_managed_network_requirements*/ false, ); if sandbox == SandboxType::None { @@ -182,7 +182,8 @@ impl FileSystemSandboxRunner { self.runtime_paths.codex_linux_sandbox_exe.as_deref() }, use_legacy_landlock: sandbox_context.use_legacy_landlock, - windows_sandbox_level: sandbox_context.windows_sandbox_level, + windows_sandbox_level: windows_sandbox_level + .unwrap_or(WindowsSandboxLevel::Disabled), windows_sandbox_private_desktop: sandbox_context .windows_sandbox_private_desktop, }, @@ -721,8 +722,8 @@ mod tests { "filesystem sandbox cannot be enforced on this executor" ); crate::FileSystemSandboxContext { - windows_sandbox_level: - codex_protocol::config_types::WindowsSandboxLevel::RestrictedToken, + windows_sandbox_selection: + codex_file_system::WindowsSandboxSelection::RestrictedToken, ..sandbox_context } }; diff --git a/codex-rs/exec-server/src/lib.rs b/codex-rs/exec-server/src/lib.rs index 141c7b7eda..c01d5368fb 100644 --- a/codex-rs/exec-server/src/lib.rs +++ b/codex-rs/exec-server/src/lib.rs @@ -36,6 +36,7 @@ mod resolved_capability; mod rpc; mod rpc_server_requests; mod runtime_paths; +mod sandbox_selection; mod sandboxed_file_open; mod sandboxed_file_system; mod server; @@ -86,6 +87,7 @@ pub use codex_file_system::WalkEntryKind; pub use codex_file_system::WalkError; pub use codex_file_system::WalkOptions; pub use codex_file_system::WalkOutcome; +pub use codex_file_system::WindowsSandboxSelection; pub use codex_file_system::WriteFileOptions; pub use codex_protocol::shell_environment::CODEX_EXEC_SERVER_NOISE_AUTH_TOKEN_ENV_VAR; pub use environment::CODEX_EXEC_SERVER_NOISE_CHATGPT_ACCOUNT_ID_ENV_VAR; diff --git a/codex-rs/exec-server/src/process_sandbox.rs b/codex-rs/exec-server/src/process_sandbox.rs index 26c59008e5..035bfa8831 100644 --- a/codex-rs/exec-server/src/process_sandbox.rs +++ b/codex-rs/exec-server/src/process_sandbox.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use crate::process_telemetry::ProcessTelemetry; use codex_exec_server_protocol::JSONRPCErrorError; +use codex_file_system::WindowsSandboxSelection; use codex_network_proxy::CUSTOM_CA_ENV_KEYS; use codex_network_proxy::ManagedNetworkSandboxContext; use codex_network_proxy::ManagedProxyRouting; @@ -22,7 +23,6 @@ use codex_sandboxing::SandboxDirectSpawnTransformRequest; use codex_sandboxing::SandboxManager; use codex_sandboxing::SandboxTransformRequest; use codex_sandboxing::SandboxType; -use codex_sandboxing::SandboxablePreference; use codex_sandboxing::WindowsSandboxFilesystemOverrides; use codex_sandboxing::WindowsSandboxProxySettingsMode; use codex_sandboxing::WindowsSandboxSpawnRequest; @@ -39,6 +39,7 @@ use crate::ExecServerRuntimePaths; use crate::protocol::ExecParams; use crate::rpc::internal_error; use crate::rpc::invalid_params; +use crate::sandbox_selection::select_sandbox; pub(crate) struct PreparedExecRequest { pub(crate) command: Vec, @@ -53,7 +54,7 @@ pub(crate) struct PreparedExecRequest { struct PreparedWindowsSandboxRequest { permission_profile: PermissionProfile, workspace_roots: Vec, - windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel, + windows_sandbox_level: WindowsSandboxLevel, proxy_enforced: bool, network_proxy_restricting_sid: Option, proxy_settings_mode: WindowsSandboxProxySettingsMode, @@ -87,7 +88,7 @@ pub(crate) async fn prepare_exec_request_with_telemetry( telemetry: &ProcessTelemetry, ) -> Result { if let Some(sandbox) = params.sandbox.as_ref() - && sandbox.windows_sandbox_level == codex_protocol::config_types::WindowsSandboxLevel::Mxc + && sandbox.windows_sandbox_selection == WindowsSandboxSelection::Mxc { if params.arg0.is_some() || sandbox.windows_sandbox_private_desktop { return Err(invalid_params( @@ -120,11 +121,9 @@ pub(crate) async fn prepare_exec_request_with_telemetry( prepare_managed_network( params.managed_network.as_ref(), network_proxy, - if params - .sandbox - .as_ref() - .is_some_and(|sandbox| sandbox.windows_sandbox_level == WindowsSandboxLevel::Mxc) - { + if params.sandbox.as_ref().is_some_and(|sandbox| { + sandbox.windows_sandbox_selection == WindowsSandboxSelection::Mxc + }) { ManagedProxyRouting::DedicatedListeners } else { ManagedProxyRouting::SharedIngress @@ -213,10 +212,10 @@ pub(crate) async fn prepare_exec_request_with_telemetry( #[cfg(target_os = "macos")] let sandbox_manager = sandbox_manager .with_allowed_symlinked_codex_home(runtime_paths.allowed_symlinked_codex_home.clone()); - let sandbox = sandbox_manager.select_initial( + let (sandbox, windows_sandbox_level) = select_sandbox( + &sandbox_manager, &permissions, - SandboxablePreference::Require, - sandbox_context.windows_sandbox_level, + sandbox_context, params.enforce_managed_network, ); if sandbox == SandboxType::None { @@ -272,7 +271,7 @@ pub(crate) async fn prepare_exec_request_with_telemetry( runtime_paths.codex_linux_sandbox_exe.as_deref() }, use_legacy_landlock: sandbox_context.use_legacy_landlock, - windows_sandbox_level: sandbox_context.windows_sandbox_level, + windows_sandbox_level: windows_sandbox_level.unwrap_or(WindowsSandboxLevel::Disabled), windows_sandbox_private_desktop: sandbox_context.windows_sandbox_private_desktop, }, }; @@ -284,10 +283,12 @@ pub(crate) async fn prepare_exec_request_with_telemetry( } .map_err(|err| invalid_params(format!("failed to prepare process sandbox: {err}")))?; let windows_sandbox = if sandbox == SandboxType::WindowsRestrictedToken { + let windows_sandbox_level = windows_sandbox_level.ok_or_else(|| { + invalid_params("restricted token sandbox requires a sandbox level".to_string()) + })?; request.arg0 = params.arg0.clone(); let proxy_enforced = params.enforce_managed_network; - let use_elevated = - windows_sandbox_uses_elevated_backend(sandbox_context.windows_sandbox_level); + let use_elevated = windows_sandbox_uses_elevated_backend(windows_sandbox_level); let filesystem_overrides = if use_elevated { resolve_windows_elevated_filesystem_overrides( sandbox, @@ -300,14 +301,14 @@ pub(crate) async fn prepare_exec_request_with_telemetry( sandbox, &permissions, &native_sandbox_policy_cwd, - sandbox_context.windows_sandbox_level, + windows_sandbox_level, ) } .map_err(|err| invalid_params(format!("failed to prepare process sandbox: {err}")))?; Some(PreparedWindowsSandboxRequest { permission_profile: permissions, workspace_roots: native_workspace_roots, - windows_sandbox_level: sandbox_context.windows_sandbox_level, + windows_sandbox_level, proxy_enforced, network_proxy_restricting_sid, proxy_settings_mode: windows_sandbox_proxy_settings_mode, diff --git a/codex-rs/exec-server/src/process_sandbox_tests.rs b/codex-rs/exec-server/src/process_sandbox_tests.rs index 3fed99c833..7e66b2e419 100644 --- a/codex-rs/exec-server/src/process_sandbox_tests.rs +++ b/codex-rs/exec-server/src/process_sandbox_tests.rs @@ -4,6 +4,8 @@ use std::sync::Arc; use std::time::Duration; use codex_exec_server_protocol::JSONRPCErrorError; +#[cfg(unix)] +use codex_file_system::WindowsSandboxSelection; #[cfg(target_os = "macos")] use codex_network_proxy::ManagedNetworkSandboxContext; use codex_network_proxy::NetworkPolicyAuditObserver; @@ -135,8 +137,7 @@ async fn sandbox_request_wraps_native_argv_on_executor() { ); let mut params = params; - params.sandbox.as_mut().unwrap().windows_sandbox_level = - codex_protocol::config_types::WindowsSandboxLevel::Mxc; + params.sandbox.as_mut().unwrap().windows_sandbox_selection = WindowsSandboxSelection::Mxc; let error = prepare_exec_request( ¶ms, HashMap::new(), @@ -620,7 +621,7 @@ async fn managed_network_honors_windows_sandbox_level(windows_sandbox_level: Win permissions.clone(), cwd_uri.clone(), ); - sandbox.windows_sandbox_level = windows_sandbox_level; + sandbox.windows_sandbox_selection = windows_sandbox_level.into(); sandbox.windows_sandbox_proxy_settings_mode = Some(codex_sandboxing::WindowsSandboxProxySettingsMode::Preserve); let proxy_config = RemoteNetworkProxyConfig::from_effective_config(&NetworkProxyConfig { diff --git a/codex-rs/exec-server/src/sandbox_selection.rs b/codex-rs/exec-server/src/sandbox_selection.rs new file mode 100644 index 0000000000..0815067c49 --- /dev/null +++ b/codex-rs/exec-server/src/sandbox_selection.rs @@ -0,0 +1,30 @@ +//! Resolves an executor sandbox context to a concrete local sandbox implementation. + +use codex_file_system::FileSystemSandboxContext; +use codex_file_system::WindowsSandboxSelection; +use codex_protocol::config_types::WindowsSandboxLevel; +use codex_protocol::models::PermissionProfile; +use codex_sandboxing::SandboxManager; +use codex_sandboxing::SandboxType; +use codex_sandboxing::SandboxablePreference; + +pub(crate) fn select_sandbox( + manager: &SandboxManager, + permission_profile: &PermissionProfile, + sandbox_context: &FileSystemSandboxContext, + has_managed_network_requirements: bool, +) -> (SandboxType, Option) { + let windows_sandbox_level = match sandbox_context.windows_sandbox_selection { + WindowsSandboxSelection::Disabled => WindowsSandboxLevel::Disabled, + WindowsSandboxSelection::RestrictedToken => WindowsSandboxLevel::RestrictedToken, + WindowsSandboxSelection::Elevated => WindowsSandboxLevel::Elevated, + WindowsSandboxSelection::Mxc => return (SandboxType::WindowsMxc, None), + }; + let sandbox_type = manager.select_initial( + permission_profile, + SandboxablePreference::Require, + windows_sandbox_level, + has_managed_network_requirements, + ); + (sandbox_type, Some(windows_sandbox_level)) +} diff --git a/codex-rs/exec-server/src/server/file_system_handler.rs b/codex-rs/exec-server/src/server/file_system_handler.rs index be6802e98c..8acc2a39c6 100644 --- a/codex-rs/exec-server/src/server/file_system_handler.rs +++ b/codex-rs/exec-server/src/server/file_system_handler.rs @@ -3,7 +3,6 @@ use std::io; use base64::Engine as _; use base64::engine::general_purpose::STANDARD; use codex_exec_server_protocol::JSONRPCErrorError; -use codex_protocol::config_types::WindowsSandboxLevel; use crate::CapabilityRootsDiscoverParams; use crate::CapabilityRootsDiscoverResponse; @@ -81,8 +80,7 @@ impl FileSystemHandler { .and_then(|root| root.sandbox.as_ref()) .filter(|sandbox| { sandbox.should_run_in_sandbox() - && (!cfg!(target_os = "windows") - || sandbox.windows_sandbox_level != WindowsSandboxLevel::Disabled) + && (!cfg!(target_os = "windows") || sandbox.windows_sandbox_is_requested()) && params .roots .iter() diff --git a/codex-rs/exec-server/tests/capability_discovery.rs b/codex-rs/exec-server/tests/capability_discovery.rs index a547c978ba..0be51f9fbb 100644 --- a/codex-rs/exec-server/tests/capability_discovery.rs +++ b/codex-rs/exec-server/tests/capability_discovery.rs @@ -12,20 +12,15 @@ use codex_exec_server::CapabilityRootsDiscoverResponse; use codex_exec_server::FileSystemSandboxContext; use codex_exec_server::InitializeParams; use codex_exec_server::InitializeResponse; +use codex_exec_server::WindowsSandboxSelection; use codex_exec_server_protocol::CapabilityRootDiscoverRequest; use codex_exec_server_protocol::JSONRPCMessage; use codex_exec_server_protocol::JSONRPCResponse; -#[cfg(unix)] use codex_protocol::models::PermissionProfile; -#[cfg(unix)] use codex_protocol::permissions::FileSystemAccessMode; -#[cfg(unix)] use codex_protocol::permissions::FileSystemSandboxEntry; -#[cfg(unix)] use codex_protocol::permissions::FileSystemSandboxPolicy; -#[cfg(unix)] use codex_protocol::permissions::NetworkSandboxPolicy; -#[cfg(unix)] use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_path_uri::PathUri; use common::exec_server::exec_server; @@ -188,10 +183,11 @@ async fn discovers_cursor_plugin_without_reading_default_mcp_for_inline_servers( Ok(()) } -#[cfg(unix)] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn sandboxed_discovery_batches_roots_without_combining_different_permissions() -> anyhow::Result<()> { + #[cfg(windows)] + crate::skip_if_mxc_unavailable!(Ok(())); let workspace = tempfile::tempdir()?; let first_root = workspace.path().join("first"); let second_root = workspace.path().join("second"); @@ -216,6 +212,7 @@ async fn sandboxed_discovery_batches_roots_without_combining_different_permissio PermissionProfile::from_runtime_permissions(&policy, NetworkSandboxPolicy::Restricted), workspace_uri, ); + let shared_sandbox = with_native_sandbox(shared_sandbox); #[cfg(target_os = "linux")] let fake_bwrap_directory = tempfile::tempdir()?; @@ -338,6 +335,7 @@ async fn sandboxed_discovery_batches_roots_without_combining_different_permissio ), first_uri.clone(), ); + let first_only_sandbox = with_native_sandbox(first_only_sandbox); let read_second_root = FileSystemSandboxEntry::new( AbsolutePathBuf::from_absolute_path(&second_root)?.into(), FileSystemAccessMode::Read, @@ -350,6 +348,7 @@ async fn sandboxed_discovery_batches_roots_without_combining_different_permissio ), second_uri.clone(), ); + let second_only_sandbox = with_native_sandbox(second_only_sandbox); let response = discover_roots( &mut server, vec![ @@ -546,3 +545,10 @@ fn write_file(path: &std::path::Path, contents: &str) -> anyhow::Result<()> { std::fs::write(path, contents)?; Ok(()) } + +fn with_native_sandbox(mut sandbox: FileSystemSandboxContext) -> FileSystemSandboxContext { + if cfg!(windows) { + sandbox.windows_sandbox_selection = WindowsSandboxSelection::Mxc; + } + sandbox +} diff --git a/codex-rs/exec-server/tests/common/mod.rs b/codex-rs/exec-server/tests/common/mod.rs index 6b3d2937d2..39eeae10d6 100644 --- a/codex-rs/exec-server/tests/common/mod.rs +++ b/codex-rs/exec-server/tests/common/mod.rs @@ -32,6 +32,16 @@ pub(crate) const SYSTEM_PROXY_URL_ENV: &str = "CODEX_EXEC_SERVER_TEST_SYSTEM_PRO const CODEX_WINDOWS_SANDBOX_ARG1: &str = "--run-as-windows-sandbox"; const DELAYED_OUTPUT_AFTER_EXIT_CHILD_ARG: &str = "--codex-test-delayed-output-after-exit-child"; +#[macro_export] +macro_rules! skip_if_mxc_unavailable { + ($return_value:expr $(,)?) => {{ + if !codex_sandboxing::windows_mxc_available() { + eprintln!("skipping test: native MXC is unavailable on this host"); + return $return_value; + } + }}; +} + #[ctor] pub static TEST_BINARY_DISPATCH_GUARD: Option = { let guard = configure_test_binary_dispatch("codex-exec-server-tests", |exe_name, argv1| { diff --git a/codex-rs/exec-server/tests/exec_process.rs b/codex-rs/exec-server/tests/exec_process.rs index ee58834c73..b4e861d50b 100644 --- a/codex-rs/exec-server/tests/exec_process.rs +++ b/codex-rs/exec-server/tests/exec_process.rs @@ -27,6 +27,8 @@ use codex_exec_server::ShellInfo; #[cfg(unix)] use codex_exec_server::ShellSnapshotRequest; use codex_exec_server::StartedExecProcess; +#[cfg(any(unix, windows))] +use codex_exec_server::WindowsSandboxSelection; use codex_exec_server::WriteStatus; #[cfg(unix)] use codex_network_proxy::NetworkProxyConfig; @@ -36,7 +38,6 @@ use codex_network_proxy::RemoteNetworkProxyConfig; use codex_network_proxy::RemoteNetworkProxyLaunchConfig; #[cfg(unix)] use codex_protocol::config_types::ShellEnvironmentPolicyInherit; -use codex_protocol::config_types::WindowsSandboxLevel; #[cfg(unix)] use codex_protocol::models::PermissionProfile; #[cfg(unix)] @@ -104,10 +105,6 @@ async fn create_process_context(use_remote: bool) -> Result { } } -fn selected_windows_sandbox_available(windows_sandbox_level: WindowsSandboxLevel) -> bool { - windows_sandbox_level != WindowsSandboxLevel::Mxc || codex_sandboxing::windows_mxc_available() -} - #[cfg(target_os = "macos")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn codex_home_symlink_opt_out_respects_host_config_and_scope() -> Result<()> { @@ -1295,13 +1292,11 @@ async fn assert_exec_process_write_then_read_without_tty(use_remote: bool) -> Re } async fn assert_remote_windows_sandbox_process_write( - windows_sandbox_level: WindowsSandboxLevel, expected_sandbox_type: codex_sandboxing::SandboxType, tty: bool, ) -> Result<()> { - if !selected_windows_sandbox_available(windows_sandbox_level) { - eprintln!("skipping MXC enforcement test: native MXC is unavailable on this host"); - return Ok(()); + if expected_sandbox_type == codex_sandboxing::SandboxType::WindowsMxc { + crate::skip_if_mxc_unavailable!(Ok(())); } let context = create_process_context(/*use_remote*/ true).await?; let workspace = TempDir::new()?; @@ -1311,7 +1306,19 @@ async fn assert_remote_windows_sandbox_process_write( SandboxPolicy::new_read_only_policy(), cwd.clone(), )?; - sandbox.windows_sandbox_level = windows_sandbox_level; + match expected_sandbox_type { + codex_sandboxing::SandboxType::WindowsRestrictedToken => { + sandbox.windows_sandbox_selection = WindowsSandboxSelection::RestrictedToken; + } + codex_sandboxing::SandboxType::WindowsMxc => { + sandbox.windows_sandbox_selection = WindowsSandboxSelection::Mxc; + } + codex_sandboxing::SandboxType::None + | codex_sandboxing::SandboxType::MacosSeatbelt + | codex_sandboxing::SandboxType::LinuxSeccomp => { + anyhow::bail!("expected a Windows sandbox type") + } + } let session = match context .backend @@ -1767,19 +1774,16 @@ async fn exec_process_write_then_read_without_tty(use_remote: bool) -> Result<() } #[test_case( - WindowsSandboxLevel::RestrictedToken, codex_sandboxing::SandboxType::WindowsRestrictedToken, false; "restricted_token" )] #[test_case( - WindowsSandboxLevel::Mxc, codex_sandboxing::SandboxType::WindowsMxc, false; "mxc_pipe" )] #[test_case( - WindowsSandboxLevel::Mxc, codex_sandboxing::SandboxType::WindowsMxc, true; "mxc_conpty" @@ -1788,12 +1792,10 @@ async fn exec_process_write_then_read_without_tty(use_remote: bool) -> Result<() #[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[serial_test::serial(remote_exec_server)] async fn remote_windows_sandbox_process_accepts_process_write( - windows_sandbox_level: WindowsSandboxLevel, expected_sandbox_type: codex_sandboxing::SandboxType, tty: bool, ) -> Result<()> { - assert_remote_windows_sandbox_process_write(windows_sandbox_level, expected_sandbox_type, tty) - .await + assert_remote_windows_sandbox_process_write(expected_sandbox_type, tty).await } #[test_case(false ; "local")] diff --git a/codex-rs/exec-server/tests/exec_process/windows_sandbox.rs b/codex-rs/exec-server/tests/exec_process/windows_sandbox.rs index 1f2868112f..f7e725ff9e 100644 --- a/codex-rs/exec-server/tests/exec_process/windows_sandbox.rs +++ b/codex-rs/exec-server/tests/exec_process/windows_sandbox.rs @@ -14,10 +14,7 @@ use pretty_assertions::assert_eq; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[serial_test::serial(remote_exec_server)] async fn mxc_tmpdir_uses_command_environment_over_rpc() -> Result<()> { - if !selected_windows_sandbox_available(WindowsSandboxLevel::Mxc) { - eprintln!("skipping MXC enforcement test: native MXC is unavailable on this host"); - return Ok(()); - } + crate::skip_if_mxc_unavailable!(Ok(())); let root = TempDir::new()?; let command_temp = root.path().join("command temp"); let server_temp = root.path().join("server temp"); @@ -53,7 +50,7 @@ async fn mxc_tmpdir_uses_command_environment_over_rpc() -> Result<()> { PermissionProfile::from_runtime_permissions(&fs, NetworkSandboxPolicy::Restricted), cwd.clone(), ); - sandbox.windows_sandbox_level = WindowsSandboxLevel::Mxc; + sandbox.windows_sandbox_selection = codex_exec_server::WindowsSandboxSelection::Mxc; let command_temp = command_temp.to_string_lossy().into_owned(); let started = environment .get_exec_backend() diff --git a/codex-rs/exec-server/tests/file_system/support.rs b/codex-rs/exec-server/tests/file_system/support.rs index 30b38bdb36..34125c24ae 100644 --- a/codex-rs/exec-server/tests/file_system/support.rs +++ b/codex-rs/exec-server/tests/file_system/support.rs @@ -7,7 +7,7 @@ use codex_exec_server::ExecServerRuntimePaths; use codex_exec_server::ExecutorFileSystem; use codex_exec_server::FileSystemSandboxContext; use codex_exec_server::LocalFileSystem; -use codex_protocol::config_types::WindowsSandboxLevel; +use codex_exec_server::WindowsSandboxSelection; use codex_protocol::models::PermissionProfile; use codex_protocol::permissions::FileSystemAccessMode; use codex_protocol::permissions::FileSystemPath; @@ -141,7 +141,7 @@ pub(crate) fn workspace_write_sandbox( PermissionProfile::from_runtime_permissions(&policy, NetworkSandboxPolicy::Restricted), PathUri::from_abs_path(&writable_root), ); - sandbox.windows_sandbox_level = WindowsSandboxLevel::RestrictedToken; + sandbox.windows_sandbox_selection = WindowsSandboxSelection::RestrictedToken; sandbox } @@ -163,7 +163,7 @@ fn sandbox_context(mut entries: Vec) -> FileSystemSandbo ), ); if cfg!(windows) { - sandbox.windows_sandbox_level = WindowsSandboxLevel::RestrictedToken; + sandbox.windows_sandbox_selection = WindowsSandboxSelection::RestrictedToken; } sandbox } diff --git a/codex-rs/exec-server/tests/file_system_windows.rs b/codex-rs/exec-server/tests/file_system_windows.rs index e80b8eb1a7..7fe9df0f8e 100644 --- a/codex-rs/exec-server/tests/file_system_windows.rs +++ b/codex-rs/exec-server/tests/file_system_windows.rs @@ -20,9 +20,10 @@ use codex_exec_server::FileSystemSandboxContext; use codex_exec_server::GetMetadataOptions; use codex_exec_server::ReadFileOptions; use codex_exec_server::RemoveOptions; +use codex_exec_server::WindowsSandboxSelection; use codex_exec_server::WriteFileOptions; -use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::protocol::SandboxPolicy; +use codex_sandboxing::SandboxType; use codex_utils_path_uri::PathUri; use futures::TryStreamExt; use pretty_assertions::assert_eq; @@ -346,8 +347,12 @@ async fn file_system_no_follow_operations_reject_named_pipes( Ok(()) } +#[test_case(SandboxType::WindowsRestrictedToken; "restricted_token")] +#[test_case(SandboxType::WindowsMxc; "mxc")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn file_system_remote_fs_helper_respects_windows_sandbox_write_policy() -> Result<()> { +async fn file_system_remote_fs_helper_respects_windows_sandbox_write_policy( + sandbox_type: SandboxType, +) -> Result<()> { let context = create_file_system_context(FileSystemImplementation::Remote).await?; let file_system = context.file_system; let tmp = tempfile::TempDir::new()?; @@ -355,7 +360,39 @@ async fn file_system_remote_fs_helper_respects_windows_sandbox_write_policy() -> std::fs::create_dir_all(&readonly_dir)?; let mut sandbox = read_only_sandbox_for_cwd(readonly_dir.clone())?; - sandbox.windows_sandbox_level = WindowsSandboxLevel::RestrictedToken; + match sandbox_type { + SandboxType::WindowsRestrictedToken => { + sandbox.windows_sandbox_selection = WindowsSandboxSelection::RestrictedToken; + } + SandboxType::WindowsMxc => { + sandbox.windows_sandbox_selection = WindowsSandboxSelection::Mxc; + } + SandboxType::None | SandboxType::MacosSeatbelt | SandboxType::LinuxSeccomp => { + anyhow::bail!("expected a Windows sandbox type") + } + } + + let blocked_file = readonly_dir.join("blocked.txt"); + if sandbox_type == SandboxType::WindowsMxc && !codex_sandboxing::windows_mxc_available() { + let error = file_system + .write_file( + &PathUri::from_host_native_path(&blocked_file)?, + b"blocked".to_vec(), + WriteFileOptions::default(), + Some(&sandbox), + ) + .await + .expect_err("unavailable MXC must fail closed"); + assert_eq!( + (error.kind(), error.to_string()), + ( + std::io::ErrorKind::InvalidInput, + "failed to prepare fs sandbox: failed to prepare MXC sandbox: native MXC is unavailable on this executor".to_owned(), + ) + ); + assert!(!blocked_file.exists()); + return Ok(()); + } let readable_file = readonly_dir.join("readable.txt"); std::fs::write(&readable_file, b"readable")?; @@ -369,12 +406,13 @@ async fn file_system_remote_fs_helper_respects_windows_sandbox_write_policy() -> // Some local Windows hosts cannot create restricted tokens. Reaching that // error still proves the remote fs helper went through the Windows sandbox // launcher; before the wrapper fix this read would have run unsandboxed. - if is_unsupported_restricted_token_host(&read_result) { + if sandbox_type == SandboxType::WindowsRestrictedToken + && is_unsupported_restricted_token_host(&read_result) + { return Ok(()); } assert_eq!(read_result?, b"readable"); - let blocked_file = readonly_dir.join("blocked.txt"); let error = file_system .write_file( &PathUri::from_host_native_path(&blocked_file)?, @@ -450,7 +488,7 @@ async fn file_system_private_desktop_survives_helper_exits_and_separates_permiss } let mut readonly = read_only_sandbox_for_cwd(tmp.path().to_path_buf())?; - readonly.windows_sandbox_level = WindowsSandboxLevel::RestrictedToken; + readonly.windows_sandbox_selection = WindowsSandboxSelection::RestrictedToken; readonly.windows_sandbox_private_desktop = true; assert_eq!( file_system diff --git a/codex-rs/ext/skills/src/provider/executor.rs b/codex-rs/ext/skills/src/provider/executor.rs index ddf2c82d91..5010614853 100644 --- a/codex-rs/ext/skills/src/provider/executor.rs +++ b/codex-rs/ext/skills/src/provider/executor.rs @@ -310,10 +310,7 @@ async fn read_bounded_text( }; if sandbox.is_some_and(FileSystemSandboxContext::should_run_in_sandbox) && path.infer_path_convention() == Some(PathConvention::Windows) - && sandbox.is_some_and(|context| { - context.windows_sandbox_level - == codex_protocol::config_types::WindowsSandboxLevel::Disabled - }) + && sandbox.is_some_and(|context| !context.windows_sandbox_is_requested()) { return Err(SkillProviderError::new( "executor skill resource requires an unavailable filesystem sandbox", diff --git a/codex-rs/ext/skills/tests/executor_file_system_authority.rs b/codex-rs/ext/skills/tests/executor_file_system_authority.rs index 1e248dc4e0..5c18550597 100644 --- a/codex-rs/ext/skills/tests/executor_file_system_authority.rs +++ b/codex-rs/ext/skills/tests/executor_file_system_authority.rs @@ -30,6 +30,7 @@ use codex_exec_server::WalkEntry; use codex_exec_server::WalkEntryKind; use codex_exec_server::WalkOptions; use codex_exec_server::WalkOutcome; +use codex_exec_server::WindowsSandboxSelection; use codex_exec_server::WriteFileOptions; use codex_protocol::capabilities::CapabilityRootLocation; use codex_protocol::capabilities::SelectedCapabilityRoot; @@ -301,12 +302,12 @@ async fn skill_loading_and_reads_use_the_supplied_executor_file_system() { } #[tokio::test] -async fn windows_executor_skill_read_rejects_disabled_sandbox_on_any_orchestrator() { +async fn windows_executor_skill_read_requires_a_requested_sandbox() { let provider = ExecutorSkillProvider::new_with_restriction_product( Arc::new(EnvironmentManager::default_for_tests()), /*restriction_product*/ None, ); - let sandbox = FileSystemSandboxContext::from_permission_profile( + let mut sandbox = FileSystemSandboxContext::from_permission_profile( PermissionProfile::from_runtime_permissions( &FileSystemSandboxPolicy::restricted(Vec::new()), NetworkSandboxPolicy::Restricted, @@ -317,6 +318,25 @@ async fn windows_executor_skill_read_rejects_disabled_sandbox_on_any_orchestrato "local", PathUri::parse("file:///C:/skill/SKILL.md").expect("Windows resource URI"), ); + let error = provider + .read(SkillReadRequest { + _lifetime: PhantomData, + authority: SkillAuthority::new(SkillSourceKind::Executor, "windows-root"), + package: SkillPackageId("skill://windows-root/C:/skill".into()), + resource: resource.clone(), + resolved_executor_roots: Vec::new(), + sandbox: Some(sandbox.clone()), + host_snapshot: None, + mcp_resources: None, + }) + .await + .expect_err("disabled Windows sandbox must fail closed"); + assert_eq!( + error.message, + "executor skill resource requires an unavailable filesystem sandbox" + ); + + sandbox.windows_sandbox_selection = WindowsSandboxSelection::Mxc; let error = provider .read(SkillReadRequest { _lifetime: PhantomData, @@ -329,11 +349,12 @@ async fn windows_executor_skill_read_rejects_disabled_sandbox_on_any_orchestrato mcp_resources: None, }) .await - .expect_err("disabled Windows sandbox must fail closed"); - - assert_eq!( - error.message, - "executor skill resource requires an unavailable filesystem sandbox" + .expect_err("synthetic Windows resource should not be readable"); + assert!( + error + .message + .starts_with("failed to read executor skill resource"), + "{error:?}" ); } diff --git a/codex-rs/file-system/src/lib.rs b/codex-rs/file-system/src/lib.rs index 255ce2bcdc..ba825c5ce8 100644 --- a/codex-rs/file-system/src/lib.rs +++ b/codex-rs/file-system/src/lib.rs @@ -325,6 +325,30 @@ impl TryFrom for PermissionProfile { } } +/// Windows sandbox choice encoded in executor RPCs. +/// +/// The serialized field retains its legacy `windowsSandboxLevel` name for compatibility, but MXC +/// is a sandbox implementation rather than a RestrictedToken level. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum WindowsSandboxSelection { + #[default] + Disabled, + RestrictedToken, + Elevated, + Mxc, +} + +impl From for WindowsSandboxSelection { + fn from(level: WindowsSandboxLevel) -> Self { + match level { + WindowsSandboxLevel::Disabled => Self::Disabled, + WindowsSandboxLevel::RestrictedToken => Self::RestrictedToken, + WindowsSandboxLevel::Elevated => Self::Elevated, + } + } +} + #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct FileSystemSandboxContext { @@ -339,7 +363,8 @@ pub struct FileSystemSandboxContext { /// Executor-local default directories used to resolve `:tmpdir` policy entries. #[serde(default, skip_serializing_if = "Option::is_none")] pub temporary_directories: Option>, - pub windows_sandbox_level: WindowsSandboxLevel, + #[serde(rename = "windowsSandboxLevel")] + pub windows_sandbox_selection: WindowsSandboxSelection, #[serde(default)] pub windows_sandbox_private_desktop: bool, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -385,7 +410,7 @@ impl FileSystemSandboxContext { workspace_roots, user_home_dir: None, temporary_directories: None, - windows_sandbox_level: WindowsSandboxLevel::Disabled, + windows_sandbox_selection: WindowsSandboxSelection::Disabled, windows_sandbox_private_desktop: false, windows_sandbox_proxy_settings_mode: None, use_legacy_landlock: false, @@ -402,6 +427,11 @@ impl FileSystemSandboxContext { && !file_system_policy.has_full_disk_write_access() } + /// Whether this context selects either supported Windows sandbox implementation. + pub fn windows_sandbox_is_requested(&self) -> bool { + self.windows_sandbox_selection != WindowsSandboxSelection::Disabled + } + /// Borrows the executor-owned paths needed to interpret filesystem policy entries. /// /// Returns `None` after the cwd has been omitted from a transport context. diff --git a/codex-rs/protocol/src/config_types.rs b/codex-rs/protocol/src/config_types.rs index 900950cb8a..b5ac448ebc 100644 --- a/codex-rs/protocol/src/config_types.rs +++ b/codex-rs/protocol/src/config_types.rs @@ -299,7 +299,6 @@ pub enum WindowsSandboxLevel { Disabled, RestrictedToken, Elevated, - Mxc, } /// Controls whether a Windows sandbox launch reconciles persistent proxy settings or preserves diff --git a/codex-rs/sandboxing/src/manager.rs b/codex-rs/sandboxing/src/manager.rs index 9f9fef3775..4805257d9c 100644 --- a/codex-rs/sandboxing/src/manager.rs +++ b/codex-rs/sandboxing/src/manager.rs @@ -333,9 +333,6 @@ impl SandboxManager { if !self.should_sandbox(permission_profile, pref, has_managed_network_requirements) { return SandboxType::None; } - if cfg!(windows) && windows_sandbox_level == WindowsSandboxLevel::Mxc { - return SandboxType::WindowsMxc; - } get_platform_sandbox(windows_sandbox_level != WindowsSandboxLevel::Disabled) .unwrap_or(SandboxType::None) } diff --git a/codex-rs/sandboxing/src/manager_tests.rs b/codex-rs/sandboxing/src/manager_tests.rs index fc24c4691c..149c741a3c 100644 --- a/codex-rs/sandboxing/src/manager_tests.rs +++ b/codex-rs/sandboxing/src/manager_tests.rs @@ -25,48 +25,6 @@ use pretty_assertions::assert_eq; use std::collections::HashMap; use tempfile::TempDir; -#[test] -fn mxc_selection_preserves_unsandboxed_requests_and_other_platforms() { - let manager = SandboxManager::new(); - let platform_sandbox = if cfg!(windows) { - SandboxType::WindowsMxc - } else { - get_platform_sandbox(/*windows_sandbox_enabled*/ false).unwrap_or(SandboxType::None) - }; - for (permissions, preference, expected) in [ - ( - PermissionProfile::read_only(), - SandboxablePreference::Auto, - platform_sandbox, - ), - ( - PermissionProfile::Disabled, - SandboxablePreference::Require, - platform_sandbox, - ), - ( - PermissionProfile::read_only(), - SandboxablePreference::Forbid, - SandboxType::None, - ), - ( - PermissionProfile::Disabled, - SandboxablePreference::Auto, - SandboxType::None, - ), - ] { - assert_eq!( - manager.select_initial( - &permissions, - preference, - WindowsSandboxLevel::Mxc, - /*has_managed_network_requirements*/ false, - ), - expected, - ); - } -} - #[test] fn danger_full_access_defaults_to_no_sandbox_without_network_requirements() { let manager = SandboxManager::new();