From e4ff78d28dc81f993cfbbf29e4fa73ecba030e4f Mon Sep 17 00:00:00 2001 From: David Wiesen Date: Sat, 25 Apr 2026 20:45:03 -0700 Subject: [PATCH] fix(windows-sandbox): normalize mapped-drive workdirs --- .../windows-sandbox-rs/src/elevated_impl.rs | 7 +++++-- codex-rs/windows-sandbox-rs/src/lib.rs | 19 +++++++++++++------ .../src/path_normalization.rs | 13 ++++++++++++- codex-rs/windows-sandbox-rs/src/spawn_prep.rs | 18 +++++++++++------- .../src/unified_exec/backends/elevated.rs | 16 +++++++++++----- 5 files changed, 52 insertions(+), 21 deletions(-) diff --git a/codex-rs/windows-sandbox-rs/src/elevated_impl.rs b/codex-rs/windows-sandbox-rs/src/elevated_impl.rs index b6e3ace1c2..75369502b1 100644 --- a/codex-rs/windows-sandbox-rs/src/elevated_impl.rs +++ b/codex-rs/windows-sandbox-rs/src/elevated_impl.rs @@ -34,6 +34,7 @@ mod windows_impl { use crate::logging::log_failure; use crate::logging::log_start; use crate::logging::log_success; + use crate::path_normalization::execution_path; use crate::policy::SandboxPolicy; use crate::policy::parse_policy; use crate::runner_client::spawn_runner_transport; @@ -126,6 +127,8 @@ mod windows_impl { write_roots_override, deny_write_paths_override, } = request; + let cwd = execution_path(cwd); + let sandbox_policy_cwd = execution_path(sandbox_policy_cwd); let policy = parse_policy(policy_json_or_preset)?; normalize_null_device_env(&mut env_map); ensure_non_interactive_pager(&mut env_map); @@ -186,10 +189,10 @@ mod windows_impl { (|| -> Result { let spawn_request = SpawnRequest { command: command.clone(), - cwd: cwd.to_path_buf(), + cwd: cwd.clone(), env: env_map.clone(), policy_json_or_preset: policy_json_or_preset.to_string(), - sandbox_policy_cwd: sandbox_policy_cwd.to_path_buf(), + sandbox_policy_cwd: sandbox_policy_cwd.clone(), codex_home: sandbox_base.clone(), real_codex_home: codex_home.to_path_buf(), cap_sids, diff --git a/codex-rs/windows-sandbox-rs/src/lib.rs b/codex-rs/windows-sandbox-rs/src/lib.rs index 8110c3237d..5725175b72 100644 --- a/codex-rs/windows-sandbox-rs/src/lib.rs +++ b/codex-rs/windows-sandbox-rs/src/lib.rs @@ -249,6 +249,7 @@ mod windows_impl { use super::logging::log_failure; use super::logging::log_success; use super::path_normalization::canonicalize_path; + use super::path_normalization::execution_path; use super::policy::SandboxPolicy; use super::process::create_process_as_user; use super::sandbox_utils::ensure_codex_home_exists; @@ -378,7 +379,7 @@ mod windows_impl { #[allow(clippy::expect_used)] let psid_generic = convert_string_sid_to_sid(&caps.workspace).expect("valid workspace SID"); - let ws_sid = workspace_cap_sid_for_cwd(codex_home, cwd)?; + let ws_sid = workspace_cap_sid_for_cwd(codex_home, ¤t_dir)?; #[allow(clippy::expect_used)] let psid_workspace = convert_string_sid_to_sid(&ws_sid).expect("valid workspace SID"); @@ -411,8 +412,9 @@ mod windows_impl { } let persist_aces = is_workspace_write; + let normalized_policy_cwd = execution_path(sandbox_policy_cwd); let AllowDenyPaths { allow, mut deny } = - compute_allow_paths(&policy, sandbox_policy_cwd, ¤t_dir, &env_map); + compute_allow_paths(&policy, &normalized_policy_cwd, ¤t_dir, &env_map); for path in additional_deny_write_paths { if path.exists() { deny.insert(path.clone()); @@ -607,13 +609,18 @@ mod windows_impl { #[allow(clippy::expect_used)] let psid_generic = unsafe { convert_string_sid_to_sid(&caps.workspace) }.expect("valid workspace SID"); - let ws_sid = workspace_cap_sid_for_cwd(codex_home, cwd)?; + let current_dir = execution_path(cwd); + let ws_sid = workspace_cap_sid_for_cwd(codex_home, ¤t_dir)?; #[allow(clippy::expect_used)] let psid_workspace = unsafe { convert_string_sid_to_sid(&ws_sid) }.expect("valid workspace SID"); - let current_dir = cwd.to_path_buf(); - let AllowDenyPaths { allow, deny } = - compute_allow_paths(sandbox_policy, sandbox_policy_cwd, ¤t_dir, env_map); + let normalized_policy_cwd = execution_path(sandbox_policy_cwd); + let AllowDenyPaths { allow, deny } = compute_allow_paths( + sandbox_policy, + &normalized_policy_cwd, + ¤t_dir, + env_map, + ); let canonical_cwd = canonicalize_path(¤t_dir); unsafe { for p in &allow { diff --git a/codex-rs/windows-sandbox-rs/src/path_normalization.rs b/codex-rs/windows-sandbox-rs/src/path_normalization.rs index fe6a932306..75a618e6f8 100644 --- a/codex-rs/windows-sandbox-rs/src/path_normalization.rs +++ b/codex-rs/windows-sandbox-rs/src/path_normalization.rs @@ -1,6 +1,14 @@ use std::path::Path; use std::path::PathBuf; +/// Normalize a path before handing it to Windows process-launch APIs. +/// +/// For existing paths this prefers the canonical form, which helps mapped-drive +/// workspaces resolve to a form the sandbox logon user can access. +pub fn execution_path(path: &Path) -> PathBuf { + canonicalize_path(path) +} + pub fn canonicalize_path(path: &Path) -> PathBuf { dunce::canonicalize(path).unwrap_or_else(|_| path.to_path_buf()) } @@ -23,6 +31,9 @@ mod tests { let windows_style = Path::new(r"C:\Users\Dev\Repo"); let slash_style = Path::new("c:/users/dev/repo"); - assert_eq!(canonical_path_key(windows_style), canonical_path_key(slash_style)); + assert_eq!( + canonical_path_key(windows_style), + canonical_path_key(slash_style) + ); } } diff --git a/codex-rs/windows-sandbox-rs/src/spawn_prep.rs b/codex-rs/windows-sandbox-rs/src/spawn_prep.rs index 56d04925b2..7cc7dd2b32 100644 --- a/codex-rs/windows-sandbox-rs/src/spawn_prep.rs +++ b/codex-rs/windows-sandbox-rs/src/spawn_prep.rs @@ -13,6 +13,7 @@ use crate::identity::SandboxCreds; use crate::identity::require_logon_sandbox_creds; use crate::logging::log_start; use crate::path_normalization::canonicalize_path; +use crate::path_normalization::execution_path; use crate::policy::SandboxPolicy; use crate::policy::parse_policy; use crate::sandbox_utils::ensure_codex_home_exists; @@ -105,11 +106,12 @@ fn prepare_spawn_context_common( normalize_null_device_env(env_map); ensure_non_interactive_pager(env_map); + let current_dir = execution_path(cwd); if inherit_path { inherit_path_env(env_map); } if add_git_safe_directory { - inject_git_safe_directory(env_map, cwd); + inject_git_safe_directory(env_map, ¤t_dir); } ensure_codex_home_exists(codex_home)?; @@ -122,7 +124,7 @@ fn prepare_spawn_context_common( Ok(SpawnContext { policy, - current_dir: cwd.to_path_buf(), + current_dir, sandbox_base, logs_base_dir, is_workspace_write, @@ -158,6 +160,7 @@ pub(crate) fn prepare_legacy_session_security( codex_home: &Path, cwd: &Path, ) -> Result { + let current_dir = execution_path(cwd); let caps = load_or_create_cap_sids(codex_home)?; let (h_token, psid_generic, psid_workspace, cap_sid_str) = unsafe { match policy { @@ -168,7 +171,7 @@ pub(crate) fn prepare_legacy_session_security( } SandboxPolicy::WorkspaceWrite { .. } => { let psid_generic = LocalSid::from_string(&caps.workspace)?; - let workspace_sid = workspace_cap_sid_for_cwd(codex_home, cwd)?; + let workspace_sid = workspace_cap_sid_for_cwd(codex_home, ¤t_dir)?; let psid_workspace = LocalSid::from_string(&workspace_sid)?; let base = get_current_token_for_restriction()?; let h_token = create_workspace_write_token_with_caps_from( @@ -273,10 +276,11 @@ pub(crate) fn prepare_elevated_spawn_context( /*inherit_path*/ true, /*add_git_safe_directory*/ true, )?; + let normalized_policy_cwd = execution_path(sandbox_policy_cwd); let AllowDenyPaths { allow, deny } = compute_allow_paths( &common.policy, - sandbox_policy_cwd, + &normalized_policy_cwd, &common.current_dir, env_map, ); @@ -289,8 +293,8 @@ pub(crate) fn prepare_elevated_spawn_context( }; let sandbox_creds = require_logon_sandbox_creds( &common.policy, - sandbox_policy_cwd, - cwd, + &normalized_policy_cwd, + &common.current_dir, env_map, codex_home, /*read_roots_override*/ None, @@ -306,7 +310,7 @@ pub(crate) fn prepare_elevated_spawn_context( vec![caps.readonly.clone()], ), SandboxPolicy::WorkspaceWrite { .. } => { - let cap_sid = workspace_cap_sid_for_cwd(codex_home, cwd)?; + let cap_sid = workspace_cap_sid_for_cwd(codex_home, &common.current_dir)?; ( LocalSid::from_string(&caps.workspace)?, vec![caps.workspace.clone(), cap_sid], 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 fd46ff09c5..2171e430be 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 @@ -7,6 +7,7 @@ use crate::ipc_framed::EmptyPayload; use crate::ipc_framed::FramedMessage; use crate::ipc_framed::Message; use crate::ipc_framed::SpawnRequest; +use crate::path_normalization::execution_path; use crate::runner_client::spawn_runner_transport; use crate::spawn_prep::prepare_elevated_spawn_context; use anyhow::Result; @@ -39,13 +40,15 @@ pub(crate) async fn spawn_windows_sandbox_session_elevated( &mut env_map, &command, )?; + let normalized_cwd = elevated.common.current_dir.clone(); + let normalized_policy_cwd = execution_path(sandbox_policy_cwd); let spawn_request = SpawnRequest { command: command.clone(), - cwd: cwd.to_path_buf(), + cwd: normalized_cwd.clone(), env: env_map.clone(), policy_json_or_preset: policy_json_or_preset.to_string(), - sandbox_policy_cwd: sandbox_policy_cwd.to_path_buf(), + sandbox_policy_cwd: normalized_policy_cwd, codex_home: elevated.common.sandbox_base.clone(), real_codex_home: codex_home.to_path_buf(), cap_sids: elevated.cap_sids.clone(), @@ -55,12 +58,15 @@ pub(crate) async fn spawn_windows_sandbox_session_elevated( use_private_desktop, }; let codex_home = codex_home.to_path_buf(); - let cwd = cwd.to_path_buf(); let sandbox_creds = elevated.sandbox_creds.clone(); let logs_base_dir = elevated.common.logs_base_dir.clone(); let transport = tokio::task::spawn_blocking(move || -> Result<_> { - let mut transport = - spawn_runner_transport(&codex_home, &cwd, &sandbox_creds, logs_base_dir.as_deref())?; + let mut transport = spawn_runner_transport( + &codex_home, + &normalized_cwd, + &sandbox_creds, + logs_base_dir.as_deref(), + )?; transport.send_spawn_request(spawn_request)?; transport.read_spawn_ready_with_timeout()?; Ok(transport)