Compare commits

...

1 Commits

Author SHA1 Message Date
David Wiesen
15f467b0a8 Handle remote Windows sandbox workspaces 2026-04-25 20:43:09 -07:00
4 changed files with 110 additions and 5 deletions

View File

@@ -71,7 +71,7 @@ impl RunnerTransport {
pub(crate) fn spawn_runner_transport(
codex_home: &Path,
cwd: &Path,
_cwd: &Path,
sandbox_creds: &SandboxCreds,
log_dir: Option<&Path>,
) -> Result<RunnerTransport> {
@@ -94,7 +94,11 @@ 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 runner_launch_cwd = runner_exe
.parent()
.unwrap_or(codex_home)
.to_path_buf();
let cwd_w = to_wide(runner_launch_cwd.as_os_str());
let user_w = to_wide(&sandbox_creds.username);
let domain_w = to_wide(".");
let password_w = to_wide(&sandbox_creds.password);

View File

@@ -321,7 +321,8 @@ mod windows_impl {
);
let mut cmdline_vec: Vec<u16> = to_wide(&runner_full_cmd);
let exe_w: Vec<u16> = to_wide(&runner_cmdline);
let cwd_w: Vec<u16> = to_wide(cwd);
let runner_launch_cwd = runner_exe.parent().unwrap_or(codex_home).to_path_buf();
let cwd_w: Vec<u16> = to_wide(runner_launch_cwd.as_os_str());
// Minimal CPWL launch: inherit env, no desktop override, no handle inheritance.
let env_block: Option<Vec<u16>> = None;
@@ -336,9 +337,10 @@ mod windows_impl {
log_note(
&format!(
"runner launch: exe={} cmdline={} cwd={}",
"runner launch: exe={} cmdline={} cwd={} requested_cwd={}",
runner_exe.display(),
runner_full_cmd,
runner_launch_cwd.display(),
cwd.display()
),
logs_base_dir,

View File

@@ -1,5 +1,6 @@
use crate::dpapi;
use crate::logging::debug_log;
use crate::path_normalization::unsupported_workspace_root_reason;
use crate::policy::SandboxPolicy;
use crate::setup::gather_read_roots;
use crate::setup::gather_write_roots;
@@ -12,7 +13,8 @@ use crate::setup::SandboxNetworkIdentity;
use crate::setup::SandboxUserRecord;
use crate::setup::SandboxUsersFile;
use crate::setup::SetupMarker;
use anyhow::anyhow;
use crate::setup_error::failure;
use crate::SetupErrorCode;
use anyhow::Context;
use anyhow::Result;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
@@ -34,6 +36,22 @@ pub struct SandboxCreds {
pub password: String,
}
fn ensure_supported_workspace_roots<'a>(
paths: impl IntoIterator<Item = &'a Path>,
) -> Result<()> {
for path in paths {
if let Some(reason) = unsupported_workspace_root_reason(path) {
return Err(failure(
SetupErrorCode::HelperUnknownError,
format!(
"Windows sandbox only supports local drive workspaces; {reason}. Move the workspace to a local drive or run outside the Windows sandbox."
),
));
}
}
Ok(())
}
/// Returns true when the on-disk setup artifacts exist and match the current
/// setup version.
///
@@ -148,6 +166,13 @@ pub fn require_logon_sandbox_creds(
let needed_write = write_roots_override
.map(<[PathBuf]>::to_vec)
.unwrap_or_else(|| gather_write_roots(policy, policy_cwd, command_cwd, env_map));
ensure_supported_workspace_roots(
[policy_cwd, command_cwd]
.into_iter()
.chain(needed_read.iter().map(PathBuf::as_path))
.chain(needed_write.iter().map(PathBuf::as_path))
.chain(deny_write_paths_override.iter().map(PathBuf::as_path)),
)?;
let network_identity = SandboxNetworkIdentity::from_policy(policy, proxy_enforced);
let desired_offline_proxy_settings =
offline_proxy_settings_from_env(env_map, network_identity);

View File

@@ -1,5 +1,9 @@
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;
pub fn canonicalize_path(path: &Path) -> PathBuf {
dunce::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
@@ -12,9 +16,58 @@ pub fn canonical_path_key(path: &Path) -> String {
.to_ascii_lowercase()
}
#[cfg(target_os = "windows")]
pub fn unsupported_workspace_root_reason(path: &Path) -> Option<String> {
let display = path.display().to_string();
let raw = path.as_os_str().to_string_lossy();
if is_unc_like_path_str(&raw) {
return Some(format!("{display} uses a UNC/network path"));
}
let drive_root = drive_root_from_path_str(&raw)?;
let drive_root_wide = crate::winutil::to_wide(&drive_root);
let drive_type = unsafe { GetDriveTypeW(drive_root_wide.as_ptr()) };
(drive_type == DRIVE_REMOTE).then(|| {
format!(
"{display} is on mapped drive {}",
drive_root.display()
)
})
}
#[cfg(target_os = "windows")]
fn is_unc_like_path_str(path: &str) -> bool {
if path.starts_with(r"\\?\UNC\")
|| path.starts_with(r"\\.\UNC\")
{
return true;
}
if path.starts_with(r"\\?\") || path.starts_with(r"\\.\") {
return false;
}
path.starts_with(r"\\") || path.starts_with("//")
}
#[cfg(target_os = "windows")]
fn drive_root_from_path_str(path: &str) -> Option<PathBuf> {
let trimmed = path
.strip_prefix(r"\\?\")
.or_else(|| path.strip_prefix(r"\\.\"))
.unwrap_or(path);
let bytes = trimmed.as_bytes();
if bytes.len() < 2 || bytes[1] != b':' {
return None;
}
Some(PathBuf::from(format!("{}:\\", trimmed.chars().next()?)))
}
#[cfg(test)]
mod tests {
use super::canonical_path_key;
#[cfg(target_os = "windows")]
use super::drive_root_from_path_str;
#[cfg(target_os = "windows")]
use super::is_unc_like_path_str;
use pretty_assertions::assert_eq;
use std::path::Path;
@@ -25,4 +78,25 @@ mod tests {
assert_eq!(canonical_path_key(windows_style), canonical_path_key(slash_style));
}
#[cfg(target_os = "windows")]
#[test]
fn detects_unc_like_workspace_paths() {
assert!(is_unc_like_path_str(r"\\server\share\repo"));
assert!(is_unc_like_path_str(r"\\?\UNC\server\share\repo"));
assert!(!is_unc_like_path_str(r"C:\repo"));
}
#[cfg(target_os = "windows")]
#[test]
fn extracts_drive_root_from_verbatim_or_normal_drive_paths() {
assert_eq!(
drive_root_from_path_str(r"L:\repo").expect("drive root"),
Path::new(r"L:\")
);
assert_eq!(
drive_root_from_path_str(r"\\?\L:\repo").expect("verbatim drive root"),
Path::new(r"L:\")
);
}
}