From a1441890a41ebd24e147e84056594b5910294e75 Mon Sep 17 00:00:00 2001 From: David Wiesen Date: Fri, 22 May 2026 10:00:48 -0700 Subject: [PATCH] fix: skip WSL UNC ACL roots in windows sandbox refresh --- codex-rs/windows-sandbox-rs/src/setup.rs | 56 ++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/codex-rs/windows-sandbox-rs/src/setup.rs b/codex-rs/windows-sandbox-rs/src/setup.rs index d768f97e2a..fa150e08b5 100644 --- a/codex-rs/windows-sandbox-rs/src/setup.rs +++ b/codex-rs/windows-sandbox-rs/src/setup.rs @@ -220,9 +220,12 @@ fn run_setup_refresh_inner( } }; // Refresh should never request elevation; ensure verb isn't set and we don't trigger UAC. + let cwd = request.codex_home.to_path_buf(); let mut cmd = Command::new(&exe); - cmd.arg(&b64).stdout(Stdio::null()).stderr(Stdio::null()); - let cwd = std::env::current_dir().unwrap_or_else(|_| request.codex_home.to_path_buf()); + cmd.arg(&b64) + .current_dir(&cwd) + .stdout(Stdio::null()) + .stderr(Stdio::null()); log_note( &format!( "setup refresh: spawning {} (cwd={}, payload_len={})", @@ -367,6 +370,24 @@ fn canonical_existing(paths: &[PathBuf]) -> Vec { .collect() } +fn normalize_windows_path_for_checks(path: &Path) -> String { + path.to_string_lossy().replace('/', "\\").to_ascii_lowercase() +} + +fn is_wsl_unc_path(path: &Path) -> bool { + let normalized = normalize_windows_path_for_checks(path); + normalized.starts_with(r"\\wsl.localhost\") + || normalized.starts_with(r"\\?\unc\wsl.localhost\") + || normalized.starts_with(r"\\wsl$\") + || normalized.starts_with(r"\\?\unc\wsl$\") +} + +fn filter_unsupported_windows_acl_roots(roots: Vec) -> Vec { + roots.into_iter() + .filter(|root| !is_wsl_unc_path(root)) + .collect() +} + fn profile_read_roots(user_profile: &Path) -> Vec { let entries = match std::fs::read_dir(user_profile) { Ok(entries) => entries, @@ -496,7 +517,8 @@ pub(crate) fn effective_write_roots_for_permissions( let write_roots = filter_user_profile_root(write_roots); let write_roots = filter_user_profile_root_exclusions(write_roots); let write_roots = filter_ssh_config_dependency_roots(write_roots); - filter_sensitive_write_roots(write_roots, codex_home) + let write_roots = filter_sensitive_write_roots(write_roots, codex_home); + filter_unsupported_windows_acl_roots(write_roots) } #[derive(Serialize)] @@ -718,6 +740,7 @@ fn run_setup_exe( if !needs_elevation { let status = Command::new(&exe) .arg(&payload_b64) + .current_dir(codex_home) .creation_flags(0x08000000) // CREATE_NO_WINDOW .stdin(Stdio::null()) .stdout(Stdio::null()) @@ -751,12 +774,14 @@ fn run_setup_exe( let params = quote_arg(&payload_b64); let params_w = crate::winutil::to_wide(params); let verb_w = crate::winutil::to_wide("runas"); + let directory_w = crate::winutil::to_wide(codex_home); let mut sei: SHELLEXECUTEINFOW = unsafe { std::mem::zeroed() }; sei.cbSize = std::mem::size_of::() as u32; sei.fMask = SEE_MASK_NOCLOSEPROCESS; sei.lpVerb = verb_w.as_ptr(); sei.lpFile = exe_w.as_ptr(); sei.lpParameters = params_w.as_ptr(); + sei.lpDirectory = directory_w.as_ptr(); // Hide the window for the elevated helper. sei.nShow = 0; // SW_HIDE let ok = unsafe { ShellExecuteExW(&mut sei) }; @@ -876,6 +901,7 @@ fn build_payload_roots( read_roots = filter_user_profile_root(read_roots); read_roots = filter_user_profile_root_exclusions(read_roots); read_roots = filter_ssh_config_dependency_roots(read_roots); + read_roots = filter_unsupported_windows_acl_roots(read_roots); let write_root_set: HashSet = write_roots.iter().cloned().collect(); read_roots.retain(|root| !write_root_set.contains(root)); (read_roots, write_roots) @@ -896,7 +922,7 @@ fn build_payload_deny_write_paths( .map(|path| canonicalize_path(&path)) .collect(); deny_write_paths.extend(allow_deny_paths.deny); - deny_write_paths + filter_unsupported_windows_acl_roots(deny_write_paths) } fn build_payload_deny_read_paths(explicit_deny_read_paths: Option>) -> Vec { @@ -1032,9 +1058,11 @@ fn filter_sensitive_write_roots(mut roots: Vec, codex_home: &Path) -> V mod tests { use super::WINDOWS_PLATFORM_DEFAULT_READ_ROOTS; use super::build_payload_roots; + use super::filter_unsupported_windows_acl_roots; use super::find_setup_exe_for_current_exe; use super::gather_full_read_roots_for_permissions; use super::gather_read_roots; + use super::is_wsl_unc_path; use super::loopback_proxy_port_from_url; use super::offline_proxy_settings_from_env; use super::profile_read_roots; @@ -1086,6 +1114,26 @@ mod tests { ); } + #[test] + fn detects_wsl_unc_paths() { + assert!(is_wsl_unc_path(PathBuf::from(r"\\wsl.localhost\Ubuntu\home\dev\repo").as_path())); + assert!(is_wsl_unc_path(PathBuf::from(r"\\?\UNC\wsl$\Ubuntu\home\dev\repo").as_path())); + assert!(!is_wsl_unc_path(PathBuf::from(r"C:\Users\dev\repo").as_path())); + } + + #[test] + fn filters_out_wsl_unc_acl_roots() { + let roots = vec![ + PathBuf::from(r"\\wsl.localhost\Ubuntu\home\dev\repo"), + PathBuf::from(r"C:\Users\dev\repo"), + ]; + + assert_eq!( + filter_unsupported_windows_acl_roots(roots), + vec![PathBuf::from(r"C:\Users\dev\repo")] + ); + } + #[test] fn setup_exe_lookup_checks_package_resource_dir_for_bin_exe() { let tmp = TempDir::new().expect("tempdir");