diff --git a/codex-rs/windows-sandbox-rs/src/elevated_impl.rs b/codex-rs/windows-sandbox-rs/src/elevated_impl.rs index 2c1c4f79ce..0294fbee4f 100644 --- a/codex-rs/windows-sandbox-rs/src/elevated_impl.rs +++ b/codex-rs/windows-sandbox-rs/src/elevated_impl.rs @@ -23,7 +23,7 @@ mod windows_impl { use crate::acl::allow_null_device; use crate::cap::load_or_create_cap_sids; use crate::env::ensure_non_interactive_pager; - use crate::env::inherit_path_env; + use crate::env::inherit_windows_bootstrap_env; use crate::env::normalize_null_device_env; use crate::identity::require_logon_sandbox_creds; use crate::ipc_framed::Message; @@ -129,7 +129,7 @@ mod windows_impl { let policy = parse_policy(policy_json_or_preset)?; normalize_null_device_env(&mut env_map); ensure_non_interactive_pager(&mut env_map); - inherit_path_env(&mut env_map); + inherit_windows_bootstrap_env(&mut env_map); inject_git_safe_directory(&mut env_map, cwd, None); // Use a temp-based log dir that the sandbox user can write. let sandbox_base = codex_home.join(".sandbox"); diff --git a/codex-rs/windows-sandbox-rs/src/env.rs b/codex-rs/windows-sandbox-rs/src/env.rs index 93275e1abd..465858663b 100644 --- a/codex-rs/windows-sandbox-rs/src/env.rs +++ b/codex-rs/windows-sandbox-rs/src/env.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use dirs_next::home_dir; use std::collections::HashMap; use std::env; @@ -6,6 +6,25 @@ use std::fs::{self, File}; use std::io::Write; use std::path::{Path, PathBuf}; +fn env_key_eq(left: &str, right: &str) -> bool { + left.eq_ignore_ascii_case(right) +} + +fn has_env_key(env_map: &HashMap, key: &str) -> bool { + env_map.keys().any(|existing| env_key_eq(existing, key)) +} + +fn inherit_env_var_with(env_map: &mut HashMap, key: &str, lookup: F) +where + F: FnOnce(&str) -> Option, +{ + if !has_env_key(env_map, key) + && let Some(value) = lookup(key) + { + env_map.insert(key.to_string(), value); + } +} + pub fn normalize_null_device_env(env_map: &mut HashMap) { let keys: Vec = env_map.keys().cloned().collect(); for k in keys { @@ -30,18 +49,34 @@ pub fn ensure_non_interactive_pager(env_map: &mut HashMap) { // Keep PATH and PATHEXT stable for callers that rely on inheriting the parent process env. pub fn inherit_path_env(env_map: &mut HashMap) { - if !env_map.contains_key("PATH") - && let Ok(path) = env::var("PATH") - { - env_map.insert("PATH".into(), path); - } - if !env_map.contains_key("PATHEXT") - && let Ok(pathext) = env::var("PATHEXT") - { - env_map.insert("PATHEXT".into(), pathext); + inherit_env_var_with(env_map, "PATH", |key| env::var(key).ok()); + inherit_env_var_with(env_map, "PATHEXT", |key| env::var(key).ok()); +} + +fn inherit_windows_bootstrap_env_with(env_map: &mut HashMap, mut lookup: F) +where + F: FnMut(&str) -> Option, +{ + for key in [ + "PATH", + "PATHEXT", + "SystemRoot", + "WINDIR", + "ComSpec", + "TEMP", + "TMP", + "USERPROFILE", + "HOMEDRIVE", + "HOMEPATH", + ] { + inherit_env_var_with(env_map, key, |name| lookup(name)); } } +pub fn inherit_windows_bootstrap_env(env_map: &mut HashMap) { + inherit_windows_bootstrap_env_with(env_map, |key| env::var(key).ok()); +} + fn prepend_path(env_map: &mut HashMap, prefix: &str) { let existing = env_map .get("PATH") @@ -172,3 +207,62 @@ pub fn apply_no_network_to_env(env_map: &mut HashMap) -> Result< reorder_pathext_for_stubs(env_map); Ok(()) } + +#[cfg(test)] +mod tests { + use super::inherit_windows_bootstrap_env_with; + use std::collections::HashMap; + + #[test] + fn inherit_windows_bootstrap_env_fills_missing_bootstrap_vars() { + let mut env_map = HashMap::from([("FOO".to_string(), "bar".to_string())]); + inherit_windows_bootstrap_env_with(&mut env_map, |key| match key { + "PATH" => Some(r"C:\Windows\System32".to_string()), + "PATHEXT" => Some(".COM;.EXE;.BAT;.CMD".to_string()), + "SystemRoot" => Some(r"C:\Windows".to_string()), + "WINDIR" => Some(r"C:\Windows".to_string()), + "ComSpec" => Some(r"C:\Windows\System32\cmd.exe".to_string()), + "TEMP" => Some(r"C:\Temp".to_string()), + "TMP" => Some(r"C:\Temp".to_string()), + "USERPROFILE" => Some(r"C:\Users\codex".to_string()), + "HOMEDRIVE" => Some("C:".to_string()), + "HOMEPATH" => Some(r"\Users\codex".to_string()), + _ => None, + }); + + assert_eq!( + env_map.get("SystemRoot").map(String::as_str), + Some(r"C:\Windows") + ); + assert_eq!( + env_map.get("ComSpec").map(String::as_str), + Some(r"C:\Windows\System32\cmd.exe") + ); + assert_eq!(env_map.get("TEMP").map(String::as_str), Some(r"C:\Temp")); + assert_eq!( + env_map.get("USERPROFILE").map(String::as_str), + Some(r"C:\Users\codex") + ); + } + + #[test] + fn inherit_windows_bootstrap_env_respects_existing_case_insensitive_entries() { + let mut env_map = HashMap::from([ + ("Path".to_string(), "custom-path".to_string()), + ("systemroot".to_string(), "custom-root".to_string()), + ]); + inherit_windows_bootstrap_env_with(&mut env_map, |key| match key { + "PATH" => Some("fallback-path".to_string()), + "SystemRoot" => Some("fallback-root".to_string()), + _ => None, + }); + + assert_eq!(env_map.get("Path").map(String::as_str), Some("custom-path")); + assert_eq!( + env_map.get("systemroot").map(String::as_str), + Some("custom-root") + ); + assert!(!env_map.contains_key("PATH")); + assert!(!env_map.contains_key("SystemRoot")); + } +} diff --git a/codex-rs/windows-sandbox-rs/src/spawn_prep.rs b/codex-rs/windows-sandbox-rs/src/spawn_prep.rs index 1b3704d6a2..fabea0bdff 100644 --- a/codex-rs/windows-sandbox-rs/src/spawn_prep.rs +++ b/codex-rs/windows-sandbox-rs/src/spawn_prep.rs @@ -7,7 +7,7 @@ use crate::cap::load_or_create_cap_sids; use crate::cap::workspace_cap_sid_for_cwd; use crate::env::apply_no_network_to_env; use crate::env::ensure_non_interactive_pager; -use crate::env::inherit_path_env; +use crate::env::inherit_windows_bootstrap_env; use crate::env::normalize_null_device_env; use crate::identity::SandboxCreds; use crate::identity::require_logon_sandbox_creds; @@ -107,7 +107,7 @@ fn prepare_spawn_context_common( normalize_null_device_env(env_map); ensure_non_interactive_pager(env_map); if inherit_path { - inherit_path_env(env_map); + inherit_windows_bootstrap_env(env_map); } if add_git_safe_directory { inject_git_safe_directory(env_map, cwd);