From e62c735e1ac522487fce65fa11d5b279383b8d87 Mon Sep 17 00:00:00 2001 From: Eva Wong Date: Mon, 4 May 2026 10:05:45 -0700 Subject: [PATCH] Thread Windows metadata targets through sessions --- codex-rs/cli/src/debug_sandbox.rs | 2 + codex-rs/core/src/exec.rs | 61 ++++++++++-------- codex-rs/core/src/sandboxing/mod.rs | 2 +- .../core/src/unified_exec/process_manager.rs | 48 +++++++++++++- .../src/unified_exec/process_manager_tests.rs | 62 +++++++++++++++++++ .../src/unified_exec/backends/elevated.rs | 4 +- .../src/unified_exec/backends/legacy.rs | 2 + .../src/unified_exec/session.rs | 5 ++ .../src/unified_exec/tests.rs | 5 ++ 9 files changed, 162 insertions(+), 29 deletions(-) diff --git a/codex-rs/cli/src/debug_sandbox.rs b/codex-rs/cli/src/debug_sandbox.rs index e9bc6a046e..a3c0639fa1 100644 --- a/codex-rs/cli/src/debug_sandbox.rs +++ b/codex-rs/cli/src/debug_sandbox.rs @@ -372,6 +372,7 @@ async fn run_command_under_windows_session( None, /*tty*/ false, /*stdin_open*/ true, + &[], config.permissions.windows_sandbox_private_desktop, ) .await @@ -386,6 +387,7 @@ async fn run_command_under_windows_session( None, /*tty*/ false, /*stdin_open*/ true, + &[], config.permissions.windows_sandbox_private_desktop, ) .await diff --git a/codex-rs/core/src/exec.rs b/codex-rs/core/src/exec.rs index c98326b300..a9f41295d1 100644 --- a/codex-rs/core/src/exec.rs +++ b/codex-rs/core/src/exec.rs @@ -407,39 +407,18 @@ pub fn build_exec_request( ExecRequest::from_sandbox_exec_request(request, options, windows_sandbox_policy_cwd) }) .map_err(CodexErr::from)?; - let use_windows_elevated_backend = windows_sandbox_uses_elevated_backend( - exec_req.windows_sandbox_level, - exec_req.network.is_some(), - ); - let sandbox_policy = exec_req.compatibility_sandbox_policy(); - exec_req.windows_sandbox_filesystem_overrides = if use_windows_elevated_backend { - resolve_windows_elevated_filesystem_overrides( - exec_req.sandbox, - &sandbox_policy, - &exec_req.file_system_sandbox_policy, - exec_req.network_sandbox_policy, - sandbox_cwd, - use_windows_elevated_backend, - ) - } else { - resolve_windows_restricted_token_filesystem_overrides( - exec_req.sandbox, - &sandbox_policy, - &exec_req.file_system_sandbox_policy, - exec_req.network_sandbox_policy, - sandbox_cwd, - exec_req.windows_sandbox_level, - ) - } - .map_err(CodexErr::UnsupportedOperation)?; + ensure_windows_sandbox_filesystem_overrides(&mut exec_req) + .map_err(CodexErr::UnsupportedOperation)?; Ok(exec_req) } pub(crate) async fn execute_exec_request( - exec_request: ExecRequest, + mut exec_request: ExecRequest, stdout_stream: Option, after_spawn: Option>, ) -> Result { + ensure_windows_sandbox_filesystem_overrides(&mut exec_request) + .map_err(CodexErr::UnsupportedOperation)?; let sandbox_policy = exec_request.compatibility_sandbox_policy(); let ExecRequest { command, @@ -489,6 +468,36 @@ pub(crate) async fn execute_exec_request( finalize_exec_result(raw_output_result, sandbox, duration) } +pub(crate) fn ensure_windows_sandbox_filesystem_overrides( + exec_req: &mut ExecRequest, +) -> std::result::Result<(), String> { + let use_windows_elevated_backend = windows_sandbox_uses_elevated_backend( + exec_req.windows_sandbox_level, + exec_req.network.is_some(), + ); + let sandbox_policy = exec_req.compatibility_sandbox_policy(); + exec_req.windows_sandbox_filesystem_overrides = if use_windows_elevated_backend { + resolve_windows_elevated_filesystem_overrides( + exec_req.sandbox, + &sandbox_policy, + &exec_req.file_system_sandbox_policy, + exec_req.network_sandbox_policy, + &exec_req.windows_sandbox_policy_cwd, + use_windows_elevated_backend, + ) + } else { + resolve_windows_restricted_token_filesystem_overrides( + exec_req.sandbox, + &sandbox_policy, + &exec_req.file_system_sandbox_policy, + exec_req.network_sandbox_policy, + &exec_req.windows_sandbox_policy_cwd, + exec_req.windows_sandbox_level, + ) + }?; + Ok(()) +} + async fn get_raw_output_result( params: ExecParams, network_sandbox_policy: NetworkSandboxPolicy, diff --git a/codex-rs/core/src/sandboxing/mod.rs b/codex-rs/core/src/sandboxing/mod.rs index 5070d8da3a..29fcfe7450 100644 --- a/codex-rs/core/src/sandboxing/mod.rs +++ b/codex-rs/core/src/sandboxing/mod.rs @@ -41,7 +41,7 @@ pub(crate) struct ExecServerEnvConfig { pub(crate) local_policy_env: HashMap, } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct ExecRequest { pub command: Vec, pub cwd: AbsolutePathBuf, diff --git a/codex-rs/core/src/unified_exec/process_manager.rs b/codex-rs/core/src/unified_exec/process_manager.rs index c67abc48d6..3c41cd0643 100644 --- a/codex-rs/core/src/unified_exec/process_manager.rs +++ b/codex-rs/core/src/unified_exec/process_manager.rs @@ -11,6 +11,8 @@ use tokio::time::Duration; use tokio::time::Instant; use tokio_util::sync::CancellationToken; +#[cfg(target_os = "windows")] +use crate::exec::WindowsProtectedMetadataMode; use crate::exec_env::CODEX_THREAD_ID_ENV_VAR; use crate::exec_env::create_env; use crate::exec_policy::ExecApprovalRequest; @@ -163,6 +165,45 @@ fn exec_server_params_for_request( } } +fn prepare_exec_request_for_open_session( + request: &ExecRequest, +) -> Result { + let mut request = request.clone(); + crate::exec::ensure_windows_sandbox_filesystem_overrides(&mut request) + .map_err(UnifiedExecError::create_process)?; + Ok(request) +} + +#[cfg(target_os = "windows")] +fn protected_metadata_targets_for_windows_session( + request: &ExecRequest, +) -> Vec { + request + .windows_sandbox_filesystem_overrides + .as_ref() + .map(|overrides| { + overrides + .protected_metadata_targets + .iter() + .map(|target| { + let mode = match target.mode { + WindowsProtectedMetadataMode::ExistingDeny => { + codex_windows_sandbox::ProtectedMetadataMode::ExistingDeny + } + WindowsProtectedMetadataMode::MissingCreationMonitor => { + codex_windows_sandbox::ProtectedMetadataMode::MissingCreationMonitor + } + }; + codex_windows_sandbox::ProtectedMetadataTarget { + path: target.path.to_path_buf(), + mode, + } + }) + .collect() + }) + .unwrap_or_default() +} + /// Borrowed process state prepared for a `write_stdin` or poll operation. struct PreparedProcessHandles { process: Arc, @@ -873,6 +914,7 @@ impl UnifiedExecProcessManager { mut spawn_lifecycle: SpawnLifecycleHandle, environment: &codex_exec_server::Environment, ) -> Result { + let request = prepare_exec_request_for_open_session(request)?; let inherited_fds = spawn_lifecycle.inherited_fds(); #[cfg(target_os = "windows")] @@ -888,6 +930,8 @@ impl UnifiedExecProcessManager { "windows sandbox: failed to resolve codex_home: {err}" )) })?; + let protected_metadata_targets = + protected_metadata_targets_for_windows_session(&request); let spawned = match request.windows_sandbox_level { codex_protocol::config_types::WindowsSandboxLevel::Elevated => { codex_windows_sandbox::spawn_windows_sandbox_session_elevated( @@ -900,6 +944,7 @@ impl UnifiedExecProcessManager { None, tty, tty, + &protected_metadata_targets, request.windows_sandbox_private_desktop, ) .await @@ -916,6 +961,7 @@ impl UnifiedExecProcessManager { None, tty, tty, + &protected_metadata_targets, request.windows_sandbox_private_desktop, ) .await @@ -938,7 +984,7 @@ 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, tty)) .await .map_err(|err| UnifiedExecError::create_process(err.to_string()))?; spawn_lifecycle.after_spawn(); 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 0c5b714161..73ec3f8358 100644 --- a/codex-rs/core/src/unified_exec/process_manager_tests.rs +++ b/codex-rs/core/src/unified_exec/process_manager_tests.rs @@ -135,6 +135,68 @@ fn exec_server_process_id_matches_unified_exec_process_id() { assert_eq!(exec_server_process_id(/*process_id*/ 4321), "4321"); } +#[test] +fn open_session_prepares_windows_metadata_overrides_for_unified_exec() { + let temp_dir = tempfile::TempDir::new().expect("tempdir"); + let cwd: codex_utils_absolute_path::AbsolutePathBuf = dunce::canonicalize(temp_dir.path()) + .expect("canonical temp dir") + .try_into() + .expect("absolute temp dir"); + let permission_profile = codex_protocol::models::PermissionProfile::workspace_write_with( + &[], + codex_protocol::permissions::NetworkSandboxPolicy::Restricted, + /*exclude_tmpdir_env_var*/ true, + /*exclude_slash_tmp*/ true, + ); + let (file_system_sandbox_policy, network_sandbox_policy) = + permission_profile.to_runtime_permissions(); + let request = ExecRequest { + command: vec![ + "cmd.exe".to_string(), + "/c".to_string(), + "echo ok".to_string(), + ], + cwd: cwd.clone(), + env: HashMap::new(), + exec_server_env_config: None, + network: None, + expiration: crate::exec::ExecExpiration::DefaultTimeout, + capture_policy: crate::exec::ExecCapturePolicy::ShellTool, + sandbox: codex_sandboxing::SandboxType::WindowsRestrictedToken, + windows_sandbox_policy_cwd: cwd.clone(), + windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::RestrictedToken, + windows_sandbox_private_desktop: false, + permission_profile, + file_system_sandbox_policy, + network_sandbox_policy, + windows_sandbox_filesystem_overrides: None, + arg0: None, + }; + + let prepared = prepare_exec_request_for_open_session(&request).expect("prepare request"); + let overrides = prepared + .windows_sandbox_filesystem_overrides + .expect("metadata overrides"); + + assert_eq!( + overrides.protected_metadata_targets, + vec![ + crate::exec::WindowsProtectedMetadataTarget { + path: cwd.join(".agents"), + mode: crate::exec::WindowsProtectedMetadataMode::MissingCreationMonitor, + }, + crate::exec::WindowsProtectedMetadataTarget { + path: cwd.join(".codex"), + mode: crate::exec::WindowsProtectedMetadataMode::MissingCreationMonitor, + }, + crate::exec::WindowsProtectedMetadataTarget { + path: cwd.join(".git"), + mode: crate::exec::WindowsProtectedMetadataMode::MissingCreationMonitor, + }, + ] + ); +} + #[tokio::test] async fn network_denial_fallback_message_names_sandbox_network_proxy() { let message = network_denial_message_for_session(/*session*/ None, /*deferred*/ None).await; diff --git a/codex-rs/windows-sandbox-rs/src/unified_exec/backends/elevated.rs b/codex-rs/windows-sandbox-rs/src/unified_exec/backends/elevated.rs index ebd1326353..3ef4828d1b 100644 --- a/codex-rs/windows-sandbox-rs/src/unified_exec/backends/elevated.rs +++ b/codex-rs/windows-sandbox-rs/src/unified_exec/backends/elevated.rs @@ -8,6 +8,7 @@ use crate::ipc_framed::FramedMessage; use crate::ipc_framed::Message; use crate::ipc_framed::SpawnRequest; use crate::runner_client::spawn_runner_transport; +use crate::setup::ProtectedMetadataTarget; use crate::spawn_prep::prepare_elevated_spawn_context; use anyhow::Result; use codex_utils_pty::ProcessDriver; @@ -29,6 +30,7 @@ pub(crate) async fn spawn_windows_sandbox_session_elevated( timeout_ms: Option, tty: bool, stdin_open: bool, + protected_metadata_targets: &[ProtectedMetadataTarget], use_private_desktop: bool, ) -> Result { let elevated = prepare_elevated_spawn_context( @@ -38,7 +40,7 @@ pub(crate) async fn spawn_windows_sandbox_session_elevated( cwd, &mut env_map, &command, - &[], + protected_metadata_targets, )?; let spawn_request = SpawnRequest { diff --git a/codex-rs/windows-sandbox-rs/src/unified_exec/backends/legacy.rs b/codex-rs/windows-sandbox-rs/src/unified_exec/backends/legacy.rs index 8458d5c8c3..911c218440 100644 --- a/codex-rs/windows-sandbox-rs/src/unified_exec/backends/legacy.rs +++ b/codex-rs/windows-sandbox-rs/src/unified_exec/backends/legacy.rs @@ -10,6 +10,7 @@ use crate::process::StderrMode; use crate::process::StdinMode; use crate::process::read_handle_loop; use crate::process::spawn_process_with_pipes; +use crate::setup::ProtectedMetadataTarget; use crate::spawn_prep::LocalSid; use crate::spawn_prep::allow_null_device_for_workspace_write; use crate::spawn_prep::apply_legacy_session_acl_rules; @@ -289,6 +290,7 @@ pub(crate) async fn spawn_windows_sandbox_session_legacy( timeout_ms: Option, tty: bool, stdin_open: bool, + _protected_metadata_targets: &[ProtectedMetadataTarget], use_private_desktop: bool, ) -> Result { let common = prepare_legacy_spawn_context( diff --git a/codex-rs/windows-sandbox-rs/src/unified_exec/session.rs b/codex-rs/windows-sandbox-rs/src/unified_exec/session.rs index ced69433d1..d6ee4f2cf0 100644 --- a/codex-rs/windows-sandbox-rs/src/unified_exec/session.rs +++ b/codex-rs/windows-sandbox-rs/src/unified_exec/session.rs @@ -9,6 +9,7 @@ mod backends; +use crate::setup::ProtectedMetadataTarget; use anyhow::Result; use codex_utils_pty::SpawnedProcess; use std::collections::HashMap; @@ -25,6 +26,7 @@ pub async fn spawn_windows_sandbox_session_legacy( timeout_ms: Option, tty: bool, stdin_open: bool, + protected_metadata_targets: &[ProtectedMetadataTarget], use_private_desktop: bool, ) -> Result { backends::legacy::spawn_windows_sandbox_session_legacy( @@ -37,6 +39,7 @@ pub async fn spawn_windows_sandbox_session_legacy( timeout_ms, tty, stdin_open, + protected_metadata_targets, use_private_desktop, ) .await @@ -53,6 +56,7 @@ pub async fn spawn_windows_sandbox_session_elevated( timeout_ms: Option, tty: bool, stdin_open: bool, + protected_metadata_targets: &[ProtectedMetadataTarget], use_private_desktop: bool, ) -> Result { backends::elevated::spawn_windows_sandbox_session_elevated( @@ -65,6 +69,7 @@ pub async fn spawn_windows_sandbox_session_elevated( timeout_ms, tty, stdin_open, + protected_metadata_targets, use_private_desktop, ) .await diff --git a/codex-rs/windows-sandbox-rs/src/unified_exec/tests.rs b/codex-rs/windows-sandbox-rs/src/unified_exec/tests.rs index 66f21807ba..f132316f9e 100644 --- a/codex-rs/windows-sandbox-rs/src/unified_exec/tests.rs +++ b/codex-rs/windows-sandbox-rs/src/unified_exec/tests.rs @@ -162,6 +162,7 @@ fn legacy_non_tty_cmd_emits_output() { Some(5_000), /*tty*/ false, /*stdin_open*/ false, + &[], /*use_private_desktop*/ true, ) .await @@ -202,6 +203,7 @@ fn legacy_non_tty_powershell_emits_output() { Some(5_000), /*tty*/ false, /*stdin_open*/ false, + &[], /*use_private_desktop*/ true, ) .await @@ -426,6 +428,7 @@ fn legacy_tty_powershell_emits_output_and_accepts_input() { Some(10_000), /*tty*/ true, /*stdin_open*/ true, + &[], /*use_private_desktop*/ true, ) .await @@ -474,6 +477,7 @@ fn legacy_tty_cmd_emits_output_and_accepts_input() { Some(10_000), /*tty*/ true, /*stdin_open*/ true, + &[], /*use_private_desktop*/ true, ) .await @@ -525,6 +529,7 @@ fn legacy_tty_cmd_default_desktop_emits_output_and_accepts_input() { Some(10_000), /*tty*/ true, /*stdin_open*/ true, + &[], /*use_private_desktop*/ false, ) .await