diff --git a/codex-rs/app-server/src/command_exec.rs b/codex-rs/app-server/src/command_exec.rs index 443117e592..1761deb9bb 100644 --- a/codex-rs/app-server/src/command_exec.rs +++ b/codex-rs/app-server/src/command_exec.rs @@ -22,6 +22,7 @@ use codex_core::config::StartedNetworkProxy; use codex_core::exec::ExecExpiration; use codex_core::exec::ExecExpirationOutcome; use codex_core::exec::IO_DRAIN_TIMEOUT_MS; +use codex_core::exec::should_use_windows_restricted_token_sandbox; use codex_core::sandboxing::ExecRequest; use codex_protocol::exec_output::bytes_to_string_smart; use codex_sandboxing::SandboxType; @@ -175,7 +176,12 @@ impl CommandExecManager { process_id: process_id.clone(), }; - if matches!(exec_request.sandbox, SandboxType::WindowsRestrictedToken) { + let compatibility_sandbox_policy = exec_request.compatibility_sandbox_policy(); + if should_use_windows_restricted_token_sandbox( + exec_request.sandbox, + &compatibility_sandbox_policy, + &exec_request.file_system_sandbox_policy, + ) { if tty || stream_stdin || stream_stdout_stderr { return Err(invalid_request( "streaming command/exec is not supported with windows sandbox", @@ -710,6 +716,32 @@ mod tests { ) } + fn windows_danger_full_access_exec_request() -> ExecRequest { + let cwd = AbsolutePathBuf::current_dir().expect("current dir"); + let sandbox_policy = codex_protocol::protocol::SandboxPolicy::DangerFullAccess; + let permission_profile = PermissionProfile::from_legacy_sandbox_policy(&sandbox_policy); + let file_system_sandbox_policy = + codex_protocol::permissions::FileSystemSandboxPolicy::from(&sandbox_policy); + ExecRequest { + command: vec!["cmd".to_string()], + cwd: cwd.clone(), + env: HashMap::new(), + exec_server_env_config: None, + network: None, + expiration: ExecExpiration::DefaultTimeout, + capture_policy: codex_core::exec::ExecCapturePolicy::ShellTool, + sandbox: SandboxType::WindowsRestrictedToken, + windows_sandbox_policy_cwd: cwd, + windows_sandbox_level: WindowsSandboxLevel::Disabled, + windows_sandbox_private_desktop: false, + permission_profile, + file_system_sandbox_policy, + network_sandbox_policy: codex_protocol::permissions::NetworkSandboxPolicy::Enabled, + windows_sandbox_filesystem_overrides: None, + arg0: None, + } + } + #[tokio::test] async fn windows_sandbox_streaming_exec_is_rejected() { let (tx, _rx) = mpsc::channel(1); @@ -743,6 +775,40 @@ mod tests { ); } + #[cfg(not(target_os = "windows"))] + #[tokio::test] + async fn windows_danger_full_access_streaming_exec_is_not_treated_as_windows_sandbox() { + let (tx, _rx) = mpsc::channel(1); + let manager = CommandExecManager::default(); + let err = manager + .start(StartCommandExecParams { + outgoing: Arc::new(OutgoingMessageSender::new( + tx, + codex_analytics::AnalyticsEventsClient::disabled(), + )), + request_id: ConnectionRequestId { + connection_id: ConnectionId(2), + request_id: codex_app_server_protocol::RequestId::Integer(43), + }, + process_id: Some("proc-43".to_string()), + exec_request: windows_danger_full_access_exec_request(), + started_network_proxy: None, + tty: false, + stream_stdin: false, + stream_stdout_stderr: true, + output_bytes_cap: None, + size: None, + }) + .await + .expect_err("non-windows hosts should still fail before the restricted-token path"); + + assert_eq!(err.code, INVALID_REQUEST_ERROR_CODE); + assert_ne!( + err.message, + "streaming command/exec is not supported with windows sandbox" + ); + } + #[cfg(not(target_os = "windows"))] #[tokio::test] async fn windows_sandbox_non_streaming_exec_uses_execution_path() { diff --git a/codex-rs/core/src/exec.rs b/codex-rs/core/src/exec.rs index fd5cd7bcdc..c1ef004875 100644 --- a/codex-rs/core/src/exec.rs +++ b/codex-rs/core/src/exec.rs @@ -482,7 +482,11 @@ async fn get_raw_output_result( >, ) -> Result { #[cfg(target_os = "windows")] - if sandbox == SandboxType::WindowsRestrictedToken { + if should_use_windows_restricted_token_sandbox( + sandbox, + sandbox_policy, + &FileSystemSandboxPolicy::from(sandbox_policy), + ) { return exec_windows_sandbox(params, sandbox_policy, windows_sandbox_filesystem_overrides) .await; } @@ -967,7 +971,7 @@ async fn exec( } #[cfg_attr(not(target_os = "windows"), allow(dead_code))] -fn should_use_windows_restricted_token_sandbox( +pub fn should_use_windows_restricted_token_sandbox( sandbox: SandboxType, sandbox_policy: &SandboxPolicy, file_system_sandbox_policy: &FileSystemSandboxPolicy, diff --git a/codex-rs/core/src/exec_tests.rs b/codex-rs/core/src/exec_tests.rs index 9d335a81c7..74e528e66b 100644 --- a/codex-rs/core/src/exec_tests.rs +++ b/codex-rs/core/src/exec_tests.rs @@ -377,6 +377,21 @@ async fn process_exec_tool_call_preserves_full_buffer_capture_policy() -> Result Ok(()) } +#[test] +fn windows_restricted_token_skips_danger_full_access_policies() { + let policy = SandboxPolicy::DangerFullAccess; + let file_system_policy = FileSystemSandboxPolicy::from(&policy); + + assert_eq!( + should_use_windows_restricted_token_sandbox( + SandboxType::WindowsRestrictedToken, + &policy, + &file_system_policy, + ), + false + ); +} + #[test] fn windows_restricted_token_skips_external_sandbox_policies() { let policy = SandboxPolicy::ExternalSandbox { diff --git a/codex-rs/core/src/unified_exec/process_manager.rs b/codex-rs/core/src/unified_exec/process_manager.rs index c67abc48d6..4c922c5615 100644 --- a/codex-rs/core/src/unified_exec/process_manager.rs +++ b/codex-rs/core/src/unified_exec/process_manager.rs @@ -11,6 +11,7 @@ use tokio::time::Duration; use tokio::time::Instant; use tokio_util::sync::CancellationToken; +use crate::exec::should_use_windows_restricted_token_sandbox; use crate::exec_env::CODEX_THREAD_ID_ENV_VAR; use crate::exec_env::create_env; use crate::exec_policy::ExecApprovalRequest; @@ -875,9 +876,13 @@ impl UnifiedExecProcessManager { ) -> Result { let inherited_fds = spawn_lifecycle.inherited_fds(); + let sandbox_policy = request.compatibility_sandbox_policy(); #[cfg(target_os = "windows")] - if request.sandbox == codex_sandboxing::SandboxType::WindowsRestrictedToken { - let sandbox_policy = request.compatibility_sandbox_policy(); + if should_use_windows_restricted_token_sandbox( + request.sandbox, + &sandbox_policy, + &request.file_system_sandbox_policy, + ) { let policy_json = serde_json::to_string(&sandbox_policy).map_err(|err| { UnifiedExecError::create_process(format!( "failed to serialize Windows sandbox policy: {err}"