Handle unsupported remote Windows sandbox workspaces

This commit is contained in:
David Wiesen
2026-04-25 20:40:28 -07:00
parent ec116a6a8e
commit f54ba811cd
3 changed files with 65 additions and 1 deletions

View File

@@ -1,5 +1,10 @@
use anyhow::Result;
use std::path::Path;
use std::path::PathBuf;
use windows_sys::Win32::Storage::FileSystem::DRIVE_REMOTE;
use windows_sys::Win32::Storage::FileSystem::GetDriveTypeW;
use crate::winutil::to_wide;
pub fn canonicalize_path(path: &Path) -> PathBuf {
dunce::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
@@ -12,9 +17,41 @@ pub fn canonical_path_key(path: &Path) -> String {
.to_ascii_lowercase()
}
pub fn ensure_windows_sandbox_local_path(path: &Path, context: &str) -> Result<()> {
let raw = path.to_string_lossy();
let normalized = normalize_windows_device_path(&raw).unwrap_or_else(|| raw.into_owned());
if normalized.starts_with(r"\\") {
anyhow::bail!(
"windows sandbox does not support {context} on UNC or mapped network paths: {}. Use a local drive workspace or run without the Windows sandbox for this session.",
path.display()
);
}
if let Some(root) = windows_drive_root(&normalized) {
let drive_type = unsafe { GetDriveTypeW(to_wide(&root).as_ptr()) };
if drive_type == DRIVE_REMOTE {
anyhow::bail!(
"windows sandbox does not support {context} on mapped network drives: {}. Use the underlying local drive path or run without the Windows sandbox for this session.",
path.display()
);
}
}
Ok(())
}
fn windows_drive_root(path: &str) -> Option<String> {
let bytes = path.as_bytes();
if bytes.len() >= 3 && bytes[0].is_ascii_alphabetic() && bytes[1] == b':' {
return Some(format!("{}:\\", path[..1].to_ascii_uppercase()));
}
None
}
#[cfg(test)]
mod tests {
use super::canonical_path_key;
use super::windows_drive_root;
use pretty_assertions::assert_eq;
use std::path::Path;
@@ -23,6 +60,16 @@ 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 windows_drive_root_extracts_drive_prefix() {
assert_eq!(windows_drive_root(r"l:\cs-web"), Some(r"L:\".to_string()));
assert_eq!(windows_drive_root(r"C:/repo"), Some(r"C:\".to_string()));
assert_eq!(windows_drive_root(r"\\video1\node\cs-web"), None);
}
}

View File

@@ -15,6 +15,7 @@ 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::ensure_windows_sandbox_local_path;
use crate::policy::SandboxPolicy;
use crate::setup_error::SetupErrorCode;
use crate::setup_error::SetupFailure;
@@ -167,6 +168,13 @@ fn run_setup_refresh_inner(
}
let (read_roots, write_roots) = build_payload_roots(&request, &overrides);
let deny_write_paths = build_payload_deny_write_paths(&request, overrides.deny_write_paths);
ensure_windows_sandbox_local_path(request.command_cwd, "sandbox working directory")?;
for root in &write_roots {
ensure_windows_sandbox_local_path(root, "sandbox writable root")?;
}
for path in &deny_write_paths {
ensure_windows_sandbox_local_path(path, "sandbox protected path")?;
}
let network_identity =
SandboxNetworkIdentity::from_policy(request.policy, request.proxy_enforced);
let offline_proxy_settings = offline_proxy_settings_from_env(request.env_map, network_identity);

View File

@@ -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::ensure_windows_sandbox_local_path;
use crate::policy::SandboxPolicy;
use crate::policy::parse_policy;
use crate::sandbox_utils::ensure_codex_home_exists;
@@ -103,6 +104,8 @@ fn prepare_spawn_context_common(
anyhow::bail!("DangerFullAccess and ExternalSandbox are not supported for sandboxing")
}
ensure_windows_sandbox_local_path(cwd, "sandbox working directory")?;
normalize_null_device_env(env_map);
ensure_non_interactive_pager(env_map);
if inherit_path {
@@ -282,6 +285,12 @@ pub(crate) fn prepare_elevated_spawn_context(
);
let write_roots: Vec<PathBuf> = allow.into_iter().collect();
let deny_write_paths: Vec<PathBuf> = deny.into_iter().collect();
for root in &write_roots {
ensure_windows_sandbox_local_path(root, "sandbox writable root")?;
}
for path in &deny_write_paths {
ensure_windows_sandbox_local_path(path, "sandbox protected path")?;
}
let write_roots_override = if common.is_workspace_write {
Some(write_roots.as_slice())
} else {