Compare commits

...

1 Commits

Author SHA1 Message Date
David Wiesen
96eb0a8aa5 fix(windows-sandbox): normalize mapped drive workspaces 2026-04-25 20:46:14 -07:00
5 changed files with 93 additions and 8 deletions

View File

@@ -84,6 +84,7 @@ features = [
"Win32_UI_WindowsAndMessaging",
"Win32_UI_Shell",
"Win32_System_Registry",
"Win32_NetworkManagement_WNet",
]
version = "0.52"

View File

@@ -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);

View File

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

View File

@@ -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<PathBuf> {
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")))
);
}
}

View File

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