Compare commits

...

1 Commits

Author SHA1 Message Date
David Wiesen
deb7dc2241 Handle remote Windows sandbox workspaces 2026-04-25 20:40:47 -07:00
4 changed files with 104 additions and 8 deletions

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::ensure_sandbox_local_path;
use crate::runner_pipe::PIPE_ACCESS_INBOUND;
use crate::runner_pipe::PIPE_ACCESS_OUTBOUND;
use crate::runner_pipe::connect_pipe;
@@ -75,6 +76,7 @@ pub(crate) fn spawn_runner_transport(
sandbox_creds: &SandboxCreds,
log_dir: Option<&Path>,
) -> Result<RunnerTransport> {
ensure_sandbox_local_path(cwd, "sandbox workspace")?;
let (pipe_in_name, pipe_out_name) = pipe_pair();
let h_pipe_in =
create_named_pipe(&pipe_in_name, PIPE_ACCESS_OUTBOUND, &sandbox_creds.username)?;

View File

@@ -38,6 +38,7 @@ mod windows_impl {
use crate::logging::log_note;
use crate::logging::log_start;
use crate::logging::log_success;
use crate::path_normalization::ensure_sandbox_local_path;
use crate::policy::SandboxPolicy;
use crate::policy::parse_policy;
use crate::token::convert_string_sid_to_sid;
@@ -241,6 +242,7 @@ mod windows_impl {
write_roots_override,
deny_write_paths_override,
} = request;
ensure_sandbox_local_path(cwd, "sandbox workspace")?;
let policy = parse_policy(policy_json_or_preset)?;
normalize_null_device_env(&mut env_map);
ensure_non_interactive_pager(&mut env_map);

View File

@@ -1,5 +1,13 @@
use anyhow::Result;
use std::path::Path;
use std::path::PathBuf;
#[cfg(target_os = "windows")]
use windows_sys::Win32::Storage::FileSystem::DRIVE_REMOTE;
#[cfg(target_os = "windows")]
use windows_sys::Win32::Storage::FileSystem::GetDriveTypeW;
#[cfg(target_os = "windows")]
use crate::winutil::to_wide;
pub fn canonicalize_path(path: &Path) -> PathBuf {
dunce::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
@@ -12,9 +20,64 @@ pub fn canonical_path_key(path: &Path) -> String {
.to_ascii_lowercase()
}
pub fn ensure_sandbox_local_path(path: &Path, context: &str) -> Result<()> {
if let Some(kind) = describe_remote_windows_path(path) {
anyhow::bail!(
"Windows sandbox does not support {kind} for {context}: {}. Use a local checkout or disable sandbox/full-access for this workspace.",
path.display()
);
}
Ok(())
}
pub fn ensure_sandbox_local_paths<'a>(
paths: impl IntoIterator<Item = &'a PathBuf>,
context: &str,
) -> Result<()> {
for path in paths {
ensure_sandbox_local_path(path, context)?;
}
Ok(())
}
fn describe_remote_windows_path(path: &Path) -> Option<&'static str> {
let path_str = path.as_os_str().to_string_lossy();
if path_str.starts_with(r"\\") || path_str.starts_with("//") {
return Some("UNC network-share paths");
}
#[cfg(target_os = "windows")]
{
if let Some(root) = drive_root(path) {
let root_wide = to_wide(root);
if unsafe { GetDriveTypeW(root_wide.as_ptr()) } == DRIVE_REMOTE {
return Some("mapped network-drive paths");
}
}
}
None
}
#[cfg(target_os = "windows")]
fn drive_root(path: &Path) -> Option<String> {
use std::path::Component;
use std::path::Prefix;
match path.components().next()? {
Component::Prefix(prefix)
if matches!(prefix.kind(), Prefix::Disk(_) | Prefix::VerbatimDisk(_)) =>
{
Some(format!(r"{}\", prefix.as_os_str().to_string_lossy()))
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::canonical_path_key;
use super::ensure_sandbox_local_path;
use pretty_assertions::assert_eq;
use std::path::Path;
@@ -23,6 +86,27 @@ 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 unc_paths_are_rejected_for_sandbox_use() {
let err =
ensure_sandbox_local_path(Path::new(r"\\server\share\workspace"), "sandbox workspace")
.expect_err("UNC path should be rejected");
assert!(
err.to_string()
.contains("Windows sandbox does not support UNC network-share paths")
);
}
#[test]
fn local_paths_are_allowed_for_sandbox_use() {
ensure_sandbox_local_path(Path::new(r"C:\Users\Dev\Repo"), "sandbox workspace")
.expect("local drive path should be allowed");
}
}

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::ensure_sandbox_local_path;
use crate::path_normalization::ensure_sandbox_local_paths;
use crate::policy::SandboxPolicy;
use crate::setup_error::SetupErrorCode;
use crate::setup_error::SetupFailure;
@@ -165,7 +167,7 @@ fn run_setup_refresh_inner(
) {
return Ok(());
}
let (read_roots, write_roots) = build_payload_roots(&request, &overrides);
let (read_roots, write_roots) = build_payload_roots(&request, &overrides)?;
let deny_write_paths = build_payload_deny_write_paths(&request, overrides.deny_write_paths);
let network_identity =
SandboxNetworkIdentity::from_policy(request.policy, request.proxy_enforced);
@@ -741,7 +743,7 @@ pub fn run_elevated_setup(
format!("failed to create sandbox dir {}: {err}", sbx_dir.display()),
)
})?;
let (read_roots, write_roots) = build_payload_roots(&request, &overrides);
let (read_roots, write_roots) = build_payload_roots(&request, &overrides)?;
let deny_write_paths = build_payload_deny_write_paths(&request, overrides.deny_write_paths);
let network_identity =
SandboxNetworkIdentity::from_policy(request.policy, request.proxy_enforced);
@@ -772,7 +774,8 @@ pub fn run_elevated_setup(
fn build_payload_roots(
request: &SandboxSetupRequest<'_>,
overrides: &SetupRootOverrides,
) -> (Vec<PathBuf>, Vec<PathBuf>) {
) -> Result<(Vec<PathBuf>, Vec<PathBuf>)> {
ensure_sandbox_local_path(request.command_cwd, "sandbox workspace")?;
let write_roots = if let Some(roots) = overrides.write_roots.as_deref() {
canonical_existing(roots)
} else {
@@ -810,7 +813,9 @@ fn build_payload_roots(
read_roots = filter_ssh_config_dependency_roots(read_roots);
let write_root_set: HashSet<PathBuf> = write_roots.iter().cloned().collect();
read_roots.retain(|root| !write_root_set.contains(root));
(read_roots, write_roots)
ensure_sandbox_local_paths(read_roots.iter(), "sandbox readable roots")?;
ensure_sandbox_local_paths(write_roots.iter(), "sandbox writable roots")?;
Ok((read_roots, write_roots))
}
fn build_payload_deny_write_paths(
@@ -1416,7 +1421,8 @@ mod tests {
proxy_enforced: false,
},
&super::SetupRootOverrides::default(),
);
)
.expect("build payload roots");
let expected_helper =
dunce::canonicalize(helper_bin_dir(&codex_home)).expect("canonical helper dir");
let expected_cwd = dunce::canonicalize(&command_cwd).expect("canonical workspace");
@@ -1466,7 +1472,8 @@ mod tests {
write_roots: None,
deny_write_paths: None,
},
);
)
.expect("build payload roots");
let expected_helper =
dunce::canonicalize(helper_bin_dir(&codex_home)).expect("canonical helper dir");
let expected_cwd = dunce::canonicalize(&command_cwd).expect("canonical workspace");
@@ -1513,7 +1520,8 @@ mod tests {
write_roots: None,
deny_write_paths: None,
},
);
)
.expect("build payload roots");
let expected_helper =
dunce::canonicalize(helper_bin_dir(&codex_home)).expect("canonical helper dir");
let expected_cwd = dunce::canonicalize(&command_cwd).expect("canonical workspace");