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_WindowsAndMessaging",
"Win32_UI_Shell", "Win32_UI_Shell",
"Win32_System_Registry", "Win32_System_Registry",
"Win32_NetworkManagement_WNet",
] ]
version = "0.52" version = "0.52"

View File

@@ -4,6 +4,7 @@ use crate::ipc_framed::Message;
use crate::ipc_framed::SpawnRequest; use crate::ipc_framed::SpawnRequest;
use crate::ipc_framed::read_frame; use crate::ipc_framed::read_frame;
use crate::ipc_framed::write_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_INBOUND;
use crate::runner_pipe::PIPE_ACCESS_OUTBOUND; use crate::runner_pipe::PIPE_ACCESS_OUTBOUND;
use crate::runner_pipe::connect_pipe; 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 mut cmdline_vec = to_wide(&runner_full_cmd);
let exe_w = to_wide(&runner_cmdline); 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 user_w = to_wide(&sandbox_creds.username);
let domain_w = to_wide("."); let domain_w = to_wide(".");
let password_w = to_wide(&sandbox_creds.password); 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_failure;
use crate::logging::log_start; use crate::logging::log_start;
use crate::logging::log_success; use crate::logging::log_success;
use crate::path_normalization::resolve_sandbox_path;
use crate::policy::SandboxPolicy; use crate::policy::SandboxPolicy;
use crate::policy::parse_policy; use crate::policy::parse_policy;
use crate::runner_client::spawn_runner_transport; use crate::runner_client::spawn_runner_transport;
@@ -184,12 +185,14 @@ mod windows_impl {
} }
(|| -> Result<CaptureResult> { (|| -> Result<CaptureResult> {
let resolved_cwd = resolve_sandbox_path(cwd);
let resolved_policy_cwd = resolve_sandbox_path(sandbox_policy_cwd);
let spawn_request = SpawnRequest { let spawn_request = SpawnRequest {
command: command.clone(), command: command.clone(),
cwd: cwd.to_path_buf(), cwd: resolved_cwd,
env: env_map.clone(), env: env_map.clone(),
policy_json_or_preset: policy_json_or_preset.to_string(), 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(), codex_home: sandbox_base.clone(),
real_codex_home: codex_home.to_path_buf(), real_codex_home: codex_home.to_path_buf(),
cap_sids, cap_sids,

View File

@@ -1,8 +1,13 @@
use crate::winutil::to_wide;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; 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 { 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 { pub fn canonical_path_key(path: &Path) -> String {
@@ -12,9 +17,65 @@ pub fn canonical_path_key(path: &Path) -> String {
.to_ascii_lowercase() .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)] #[cfg(test)]
mod tests { mod tests {
use super::canonical_path_key; use super::canonical_path_key;
use super::split_mapped_drive_path;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use std::path::Path; use std::path::Path;
@@ -23,6 +84,22 @@ mod tests {
let windows_style = Path::new(r"C:\Users\Dev\Repo"); let windows_style = Path::new(r"C:\Users\Dev\Repo");
let slash_style = Path::new("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::helper_materialization::helper_bin_dir;
use crate::logging::log_note; use crate::logging::log_note;
use crate::path_normalization::canonical_path_key; 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::policy::SandboxPolicy;
use crate::setup_error::SetupErrorCode; use crate::setup_error::SetupErrorCode;
use crate::setup_error::SetupFailure; use crate::setup_error::SetupFailure;
@@ -177,7 +179,7 @@ fn run_setup_refresh_inner(
offline_username: OFFLINE_USERNAME.to_string(), offline_username: OFFLINE_USERNAME.to_string(),
online_username: ONLINE_USERNAME.to_string(), online_username: ONLINE_USERNAME.to_string(),
codex_home: request.codex_home.to_path_buf(), 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, read_roots,
write_roots, write_roots,
deny_write_paths, deny_write_paths,
@@ -322,7 +324,7 @@ fn canonical_existing(paths: &[PathBuf]) -> Vec<PathBuf> {
if !p.exists() { if !p.exists() {
return None; return None;
} }
Some(dunce::canonicalize(p).unwrap_or_else(|_| p.clone())) Some(canonicalize_path(p))
}) })
.collect() .collect()
} }
@@ -727,7 +729,7 @@ pub fn run_elevated_setup(
offline_username: OFFLINE_USERNAME.to_string(), offline_username: OFFLINE_USERNAME.to_string(),
online_username: ONLINE_USERNAME.to_string(), online_username: ONLINE_USERNAME.to_string(),
codex_home: request.codex_home.to_path_buf(), 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, read_roots,
write_roots, write_roots,
deny_write_paths, deny_write_paths,