Compare commits

...

1 Commits

Author SHA1 Message Date
David Wiesen
3f78270437 fix: isolate windows sandbox temp dir 2026-03-25 11:08:45 -07:00
4 changed files with 102 additions and 2 deletions

View File

@@ -6,6 +6,7 @@ mod windows_impl {
use crate::env::ensure_non_interactive_pager;
use crate::env::inherit_path_env;
use crate::env::normalize_null_device_env;
use crate::env::redirect_temp_env_to_codex_home;
use crate::helper_materialization::resolve_helper_for_launch;
use crate::helper_materialization::HelperExecutable;
use crate::identity::require_logon_sandbox_creds;
@@ -216,6 +217,7 @@ mod windows_impl {
normalize_null_device_env(&mut env_map);
ensure_non_interactive_pager(&mut env_map);
inherit_path_env(&mut env_map);
redirect_temp_env_to_codex_home(&mut env_map, codex_home)?;
inject_git_safe_directory(&mut env_map, cwd, None);
let current_dir = cwd.to_path_buf();
// Use a temp-based log dir that the sandbox user can write.

View File

@@ -18,6 +18,19 @@ pub fn normalize_null_device_env(env_map: &mut HashMap<String, String>) {
}
}
pub fn redirect_temp_env_to_codex_home(
env_map: &mut HashMap<String, String>,
codex_home: &Path,
) -> Result<PathBuf> {
let temp_dir = codex_home.join(".sandbox-tmp");
fs::create_dir_all(&temp_dir)?;
let temp_value = temp_dir.to_string_lossy().to_string();
env_map.insert("TEMP".into(), temp_value.clone());
env_map.insert("TMP".into(), temp_value.clone());
env_map.insert("TMPDIR".into(), temp_value);
Ok(temp_dir)
}
pub fn ensure_non_interactive_pager(env_map: &mut HashMap<String, String>) {
env_map
.entry("GIT_PAGER".into())
@@ -172,3 +185,30 @@ pub fn apply_no_network_to_env(env_map: &mut HashMap<String, String>) -> Result<
reorder_pathext_for_stubs(env_map);
Ok(())
}
#[cfg(test)]
mod tests {
use super::redirect_temp_env_to_codex_home;
use std::collections::HashMap;
use tempfile::TempDir;
#[test]
fn redirects_temp_env_to_codex_home_managed_dir() {
let tmp = TempDir::new().expect("tempdir");
let codex_home = tmp.path().join("codex-home");
let mut env_map = HashMap::from([
("TEMP".to_string(), r"C:\Users\alice\AppData\Local\Temp".to_string()),
("TMP".to_string(), r"C:\Users\alice\AppData\Local\Temp".to_string()),
]);
let temp_dir =
redirect_temp_env_to_codex_home(&mut env_map, &codex_home).expect("redirect temp");
let temp_value = temp_dir.to_string_lossy().to_string();
assert_eq!(codex_home.join(".sandbox-tmp"), temp_dir);
assert_eq!(Some(&temp_value), env_map.get("TEMP"));
assert_eq!(Some(&temp_value), env_map.get("TMP"));
assert_eq!(Some(&temp_value), env_map.get("TMPDIR"));
assert!(temp_dir.is_dir(), "redirected temp dir should exist");
}
}

View File

@@ -184,6 +184,7 @@ mod windows_impl {
use super::env::apply_no_network_to_env;
use super::env::ensure_non_interactive_pager;
use super::env::normalize_null_device_env;
use super::env::redirect_temp_env_to_codex_home;
use super::logging::log_failure;
use super::logging::log_start;
use super::logging::log_success;
@@ -299,6 +300,7 @@ mod windows_impl {
let apply_network_block = should_apply_network_block(&policy);
normalize_null_device_env(&mut env_map);
ensure_non_interactive_pager(&mut env_map);
redirect_temp_env_to_codex_home(&mut env_map, codex_home)?;
if apply_network_block {
apply_no_network_to_env(&mut env_map)?;
}

View File

@@ -11,6 +11,7 @@ use std::process::Stdio;
use crate::allow::compute_allow_paths;
use crate::allow::AllowDenyPaths;
use crate::env::redirect_temp_env_to_codex_home;
use crate::helper_materialization::helper_bin_dir;
use crate::logging::log_note;
use crate::path_normalization::canonical_path_key;
@@ -133,11 +134,13 @@ fn run_setup_refresh_inner(
) {
return Ok(());
}
let mut effective_env_map = env_map.clone();
redirect_temp_env_to_codex_home(&mut effective_env_map, codex_home)?;
let (read_roots, write_roots) = build_payload_roots(
policy,
policy_cwd,
command_cwd,
env_map,
&effective_env_map,
codex_home,
read_roots_override,
write_roots_override,
@@ -590,11 +593,13 @@ pub fn run_elevated_setup(
format!("failed to create sandbox dir {}: {err}", sbx_dir.display()),
)
})?;
let mut effective_env_map = env_map.clone();
redirect_temp_env_to_codex_home(&mut effective_env_map, codex_home)?;
let (read_roots, write_roots) = build_payload_roots(
policy,
policy_cwd,
command_cwd,
env_map,
&effective_env_map,
codex_home,
read_roots_override,
write_roots_override,
@@ -671,15 +676,18 @@ fn filter_sensitive_write_roots(mut roots: Vec<PathBuf>, codex_home: &Path) -> V
#[cfg(test)]
mod tests {
use super::build_payload_roots;
use super::gather_legacy_full_read_roots;
use super::gather_read_roots;
use super::profile_read_roots;
use super::WINDOWS_PLATFORM_DEFAULT_READ_ROOTS;
use crate::env::redirect_temp_env_to_codex_home;
use crate::helper_materialization::helper_bin_dir;
use crate::policy::SandboxPolicy;
use codex_protocol::protocol::ReadOnlyAccess;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
use std::collections::HashMap;
use std::collections::HashSet;
use std::fs;
use std::path::PathBuf;
@@ -723,6 +731,54 @@ mod tests {
assert_eq!(vec![missing_profile], roots);
}
#[test]
fn build_payload_roots_uses_codex_managed_temp_dir() {
let tmp = TempDir::new().expect("tempdir");
let command_cwd = tmp.path().join("workspace");
let original_temp = tmp.path().join("AppData").join("Local").join("Temp");
let codex_home = tmp.path().join("codex-home");
fs::create_dir_all(&command_cwd).expect("create workspace");
fs::create_dir_all(&original_temp).expect("create original temp");
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: ReadOnlyAccess::Full,
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
};
let mut env_map = HashMap::from([
(
"TEMP".to_string(),
original_temp.to_string_lossy().to_string(),
),
(
"TMP".to_string(),
original_temp.to_string_lossy().to_string(),
),
]);
let redirected_temp =
redirect_temp_env_to_codex_home(&mut env_map, &codex_home).expect("redirect temp");
let (_read_roots, write_roots) = build_payload_roots(
&policy,
&command_cwd,
&command_cwd,
&env_map,
&codex_home,
None,
None,
);
assert!(
write_roots.contains(&dunce::canonicalize(&command_cwd).expect("canonical workspace"))
);
assert!(write_roots
.contains(&dunce::canonicalize(&redirected_temp).expect("canonical redirected temp")));
assert!(!write_roots
.contains(&dunce::canonicalize(&original_temp).expect("canonical original temp")));
}
#[test]
fn gather_read_roots_includes_helper_bin_dir() {
let tmp = TempDir::new().expect("tempdir");