Compare commits

...

1 Commits

Author SHA1 Message Date
David Wiesen
5d6066b2ba Fix Windows danger-full-access exec dispatch 2026-05-10 22:04:47 -07:00
4 changed files with 97 additions and 5 deletions

View File

@@ -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,41 @@ 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() {

View File

@@ -482,7 +482,11 @@ async fn get_raw_output_result(
>,
) -> Result<RawExecToolCallOutput> {
#[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,

View File

@@ -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 {

View File

@@ -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;
@@ -874,8 +875,13 @@ impl UnifiedExecProcessManager {
let inherited_fds = spawn_lifecycle.inherited_fds();
#[cfg(target_os = "windows")]
if request.sandbox == codex_sandboxing::SandboxType::WindowsRestrictedToken {
let sandbox_policy = request.compatibility_sandbox_policy();
let sandbox_policy = request.compatibility_sandbox_policy();
#[cfg(target_os = "windows")]
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}"