diff --git a/codex-rs/windows-sandbox-rs/Cargo.toml b/codex-rs/windows-sandbox-rs/Cargo.toml index e7ca02fed2..8a6b4a66d1 100644 --- a/codex-rs/windows-sandbox-rs/Cargo.toml +++ b/codex-rs/windows-sandbox-rs/Cargo.toml @@ -84,6 +84,7 @@ features = [ "Win32_UI_WindowsAndMessaging", "Win32_UI_Shell", "Win32_System_Registry", + "Win32_NetworkManagement_WNet", ] version = "0.52" diff --git a/codex-rs/windows-sandbox-rs/src/elevated/runner_client.rs b/codex-rs/windows-sandbox-rs/src/elevated/runner_client.rs index f296e6b6e8..1baad20f2b 100644 --- a/codex-rs/windows-sandbox-rs/src/elevated/runner_client.rs +++ b/codex-rs/windows-sandbox-rs/src/elevated/runner_client.rs @@ -4,6 +4,7 @@ use crate::ipc_framed::Message; use crate::ipc_framed::SpawnRequest; use crate::ipc_framed::read_frame; use crate::ipc_framed::write_frame; +use crate::path_normalization::resolve_sandbox_path; use crate::runner_pipe::PIPE_ACCESS_INBOUND; use crate::runner_pipe::PIPE_ACCESS_OUTBOUND; use crate::runner_pipe::connect_pipe; @@ -98,7 +99,8 @@ pub(crate) fn spawn_runner_transport( ); let mut cmdline_vec = to_wide(&runner_full_cmd); let exe_w = to_wide(&runner_cmdline); - let cwd_w = to_wide(cwd); + let resolved_cwd = resolve_sandbox_path(cwd); + let cwd_w = to_wide(&resolved_cwd); let user_w = to_wide(&sandbox_creds.username); let domain_w = to_wide("."); let password_w = to_wide(&sandbox_creds.password); diff --git a/codex-rs/windows-sandbox-rs/src/elevated_impl.rs b/codex-rs/windows-sandbox-rs/src/elevated_impl.rs index b6e3ace1c2..72c506dbd4 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::resolve_sandbox_path; use crate::policy::SandboxPolicy; use crate::policy::parse_policy; use crate::runner_client::spawn_runner_transport; @@ -184,12 +185,14 @@ mod windows_impl { } (|| -> Result { + let resolved_cwd = resolve_sandbox_path(cwd); + let resolved_policy_cwd = resolve_sandbox_path(sandbox_policy_cwd); let spawn_request = SpawnRequest { command: command.clone(), - cwd: cwd.to_path_buf(), + cwd: resolved_cwd, 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: resolved_policy_cwd, codex_home: sandbox_base.clone(), real_codex_home: codex_home.to_path_buf(), cap_sids, diff --git a/codex-rs/windows-sandbox-rs/src/path_normalization.rs b/codex-rs/windows-sandbox-rs/src/path_normalization.rs index fe6a932306..bcddb23f03 100644 --- a/codex-rs/windows-sandbox-rs/src/path_normalization.rs +++ b/codex-rs/windows-sandbox-rs/src/path_normalization.rs @@ -1,8 +1,13 @@ +use crate::winutil::to_wide; use std::path::Path; use std::path::PathBuf; +use windows_sys::Win32::Foundation::ERROR_MORE_DATA; +use windows_sys::Win32::Foundation::NO_ERROR; +use windows_sys::Win32::NetworkManagement::WNet::WNetGetConnectionW; pub fn canonicalize_path(path: &Path) -> PathBuf { - dunce::canonicalize(path).unwrap_or_else(|_| path.to_path_buf()) + let mapped_path = resolve_sandbox_path(path); + dunce::canonicalize(&mapped_path).unwrap_or(mapped_path) } pub fn canonical_path_key(path: &Path) -> String { @@ -12,9 +17,65 @@ pub fn canonical_path_key(path: &Path) -> String { .to_ascii_lowercase() } +pub fn resolve_sandbox_path(path: &Path) -> PathBuf { + resolve_mapped_drive_path(path).unwrap_or_else(|| path.to_path_buf()) +} + +fn resolve_mapped_drive_path(path: &Path) -> Option { + let (drive, suffix) = split_mapped_drive_path(path)?; + let drive_w = to_wide(drive); + let mut remote_len = 0u32; + let mut status = + unsafe { WNetGetConnectionW(drive_w.as_ptr(), std::ptr::null_mut(), &mut remote_len) }; + if status != ERROR_MORE_DATA && status != NO_ERROR { + return None; + } + + let mut remote_buf = vec![0u16; remote_len as usize + 1]; + status = + unsafe { WNetGetConnectionW(drive_w.as_ptr(), remote_buf.as_mut_ptr(), &mut remote_len) }; + if status != NO_ERROR { + return None; + } + + let remote_end = remote_buf + .iter() + .position(|ch| *ch == 0) + .unwrap_or(remote_buf.len()); + let remote = String::from_utf16_lossy(&remote_buf[..remote_end]); + if remote.is_empty() { + return None; + } + + let mut resolved = PathBuf::from(remote); + if let Some(suffix) = suffix { + resolved.push(suffix); + } + Some(resolved) +} + +fn split_mapped_drive_path(path: &Path) -> Option<(&str, Option<&str>)> { + let raw = path.to_str()?; + let bytes = raw.as_bytes(); + if bytes.len() < 2 || !bytes[0].is_ascii_alphabetic() || bytes[1] != b':' { + return None; + } + if bytes.len() > 2 && bytes[2] != b'\\' && bytes[2] != b'/' { + return None; + } + + let suffix = if bytes.len() > 3 { + Some(&raw[3..]) + } else { + None + }; + Some((&raw[..2], suffix)) +} + #[cfg(test)] mod tests { use super::canonical_path_key; + use super::split_mapped_drive_path; use pretty_assertions::assert_eq; use std::path::Path; @@ -23,6 +84,22 @@ 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) + ); + } + + #[test] + fn split_mapped_drive_path_keeps_drive_relative_paths_unchanged() { + assert_eq!(split_mapped_drive_path(Path::new(r"L:repo")), None); + } + + #[test] + fn split_mapped_drive_path_extracts_drive_root_suffix() { + assert_eq!( + split_mapped_drive_path(Path::new(r"L:\cs-web\context")), + Some(("L:", Some("cs-web\\context"))) + ); } } diff --git a/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs b/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs index 94dd3574b0..d62420d515 100644 --- a/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs +++ b/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs @@ -15,6 +15,8 @@ use crate::allow::compute_allow_paths; use crate::helper_materialization::helper_bin_dir; use crate::logging::log_note; use crate::path_normalization::canonical_path_key; +use crate::path_normalization::canonicalize_path; +use crate::path_normalization::resolve_sandbox_path; use crate::policy::SandboxPolicy; use crate::setup_error::SetupErrorCode; use crate::setup_error::SetupFailure; @@ -177,7 +179,7 @@ fn run_setup_refresh_inner( offline_username: OFFLINE_USERNAME.to_string(), online_username: ONLINE_USERNAME.to_string(), codex_home: request.codex_home.to_path_buf(), - command_cwd: request.command_cwd.to_path_buf(), + command_cwd: resolve_sandbox_path(request.command_cwd), read_roots, write_roots, deny_write_paths, @@ -322,7 +324,7 @@ fn canonical_existing(paths: &[PathBuf]) -> Vec { if !p.exists() { return None; } - Some(dunce::canonicalize(p).unwrap_or_else(|_| p.clone())) + Some(canonicalize_path(p)) }) .collect() } @@ -727,7 +729,7 @@ pub fn run_elevated_setup( offline_username: OFFLINE_USERNAME.to_string(), online_username: ONLINE_USERNAME.to_string(), codex_home: request.codex_home.to_path_buf(), - command_cwd: request.command_cwd.to_path_buf(), + command_cwd: resolve_sandbox_path(request.command_cwd), read_roots, write_roots, deny_write_paths,