Compare commits

...

1 Commits

Author SHA1 Message Date
David Wiesen
cd9154a5ff fix: materialize Windows sandbox helpers outside WindowsApps 2026-04-06 09:21:30 -07:00
2 changed files with 96 additions and 2 deletions

View File

@@ -16,18 +16,21 @@ use crate::sandbox_bin_dir;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(crate) enum HelperExecutable {
CommandRunner,
Setup,
}
impl HelperExecutable {
fn file_name(self) -> &'static str {
match self {
Self::CommandRunner => "codex-command-runner.exe",
Self::Setup => "codex-windows-sandbox-setup.exe",
}
}
fn label(self) -> &'static str {
match self {
Self::CommandRunner => "command-runner",
Self::Setup => "setup-helper",
}
}
}
@@ -49,7 +52,7 @@ pub(crate) fn legacy_lookup(kind: HelperExecutable) -> PathBuf {
&& let Some(dir) = exe.parent()
{
let candidate = dir.join(kind.file_name());
if candidate.exists() {
if candidate.exists() && !is_windows_apps_path(dir) {
return candidate;
}
}
@@ -182,6 +185,13 @@ fn sibling_source_path(kind: HelperExecutable) -> Result<PathBuf> {
let dir = exe
.parent()
.ok_or_else(|| anyhow!("current executable has no parent directory"))?;
if is_windows_apps_path(dir) {
return Err(anyhow!(
"refusing to source {} from WindowsApps directory {}",
kind.label(),
dir.display()
));
}
let candidate = dir.join(kind.file_name());
if candidate.exists() {
Ok(candidate)
@@ -287,7 +297,28 @@ fn destination_is_fresh(source: &Path, destination: &Path) -> Result<bool> {
.modified()
.with_context(|| format!("read helper destination mtime {}", destination.display()))?;
Ok(destination_modified >= source_modified)
if destination_modified < source_modified {
return Ok(false);
}
files_match(source, destination)
}
fn files_match(source: &Path, destination: &Path) -> Result<bool> {
let source_bytes = fs::read(source)
.with_context(|| format!("read helper source bytes {}", source.display()))?;
let destination_bytes = fs::read(destination)
.with_context(|| format!("read helper destination bytes {}", destination.display()))?;
Ok(source_bytes == destination_bytes)
}
fn is_windows_apps_path(path: &Path) -> bool {
path.components().any(|component| {
component
.as_os_str()
.to_string_lossy()
.eq_ignore_ascii_case("WindowsApps")
})
}
#[cfg(test)]
@@ -347,6 +378,21 @@ mod tests {
assert_eq!(b"runner-v1".as_slice(), fs::read(&destination).expect("read destination"));
}
#[test]
fn destination_is_not_fresh_when_content_differs_with_same_size() {
let tmp = TempDir::new().expect("tempdir");
let source = tmp.path().join("source.exe");
let destination = tmp.path().join("destination.exe");
fs::write(&source, b"runner-v1").expect("write source");
fs::write(&destination, b"runner-v2").expect("write destination");
assert!(
!destination_is_fresh(&source, &destination)
.expect("content drift should mark destination stale")
);
}
#[test]
fn helper_bin_dir_is_under_sandbox_bin() {
let codex_home = Path::new(r"C:\Users\example\.codex");
@@ -376,4 +422,17 @@ mod tests {
fs::read(&runner_destination).expect("read runner")
);
}
#[test]
fn windows_apps_paths_are_detected_case_insensitively() {
assert!(super::is_windows_apps_path(Path::new(
r"C:\Program Files\WindowsApps\OpenAI.Codex\codex.exe"
)));
assert!(super::is_windows_apps_path(Path::new(
r"c:\program files\windowsapps\OpenAI.Codex"
)));
assert!(!super::is_windows_apps_path(Path::new(
r"C:\Users\example\.codex\.sandbox-bin"
)));
}
}

View File

@@ -12,7 +12,9 @@ use std::process::Stdio;
use crate::allow::AllowDenyPaths;
use crate::allow::compute_allow_paths;
use crate::helper_materialization::HelperExecutable;
use crate::helper_materialization::helper_bin_dir;
use crate::helper_materialization::resolve_helper_for_launch;
use crate::logging::log_note;
use crate::path_normalization::canonical_path_key;
use crate::policy::SandboxPolicy;
@@ -334,6 +336,7 @@ fn gather_helper_read_roots(codex_home: &Path) -> Vec<PathBuf> {
let mut roots = Vec::new();
if let Ok(exe) = std::env::current_exe()
&& let Some(dir) = exe.parent()
&& !is_windows_apps_path(dir)
{
roots.push(dir.to_path_buf());
}
@@ -343,6 +346,15 @@ fn gather_helper_read_roots(codex_home: &Path) -> Vec<PathBuf> {
roots
}
fn is_windows_apps_path(path: &Path) -> bool {
path.components().any(|component| {
component
.as_os_str()
.to_string_lossy()
.eq_ignore_ascii_case("WindowsApps")
})
}
fn gather_legacy_full_read_roots(
command_cwd: &Path,
policy: &SandboxPolicy,
@@ -570,8 +582,17 @@ fn quote_arg(arg: &str) -> String {
}
fn find_setup_exe() -> PathBuf {
if let Ok(codex_home) = crate::codex_home::find_codex_home() {
return resolve_helper_for_launch(
HelperExecutable::Setup,
&codex_home,
Some(&sandbox_dir(&codex_home)),
);
}
if let Ok(exe) = std::env::current_exe()
&& let Some(dir) = exe.parent()
&& !is_windows_apps_path(dir)
{
let candidate = dir.join("codex-windows-sandbox-setup.exe");
if candidate.exists() {
@@ -804,6 +825,7 @@ mod tests {
use super::WINDOWS_PLATFORM_DEFAULT_READ_ROOTS;
use super::gather_legacy_full_read_roots;
use super::gather_read_roots;
use super::is_windows_apps_path;
use super::loopback_proxy_port_from_url;
use super::offline_proxy_settings_from_env;
use super::profile_read_roots;
@@ -963,6 +985,19 @@ mod tests {
);
}
#[test]
fn windows_apps_paths_are_detected_case_insensitively() {
assert!(is_windows_apps_path(
PathBuf::from(r"C:\Program Files\WindowsApps\OpenAI.Codex\codex.exe").as_path()
));
assert!(is_windows_apps_path(
PathBuf::from(r"c:\program files\windowsapps\OpenAI.Codex").as_path()
));
assert!(!is_windows_apps_path(
PathBuf::from(r"C:\Users\example\.codex\.sandbox-bin").as_path()
));
}
#[test]
fn profile_read_roots_excludes_configured_top_level_entries() {
let tmp = TempDir::new().expect("tempdir");