From 75578d2e4cd634a7b583791a0fb3ec95449c7123 Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Mon, 12 Oct 2026 19:54:27 +0900 Subject: [PATCH] fix(diagnostics): preserve home guards in isolated child environments Split the diagnostic guard from PR #719 and retain the protected home across fixture environment replacement. Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../windows-version-control/README.md | 6 +++ .../windows-version-control/src/control.rs | 12 +++++- .../src/fixture_env.rs | 15 +++++++ .../windows-version-control/src/main.rs | 2 + .../tests/async_contracts.test.rs | 39 +++++++++++++++++++ 5 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 scripts/diagnostics/windows-version-control/src/fixture_env.rs diff --git a/scripts/diagnostics/windows-version-control/README.md b/scripts/diagnostics/windows-version-control/README.md index 6f6402553a1..4f078216b1f 100644 --- a/scripts/diagnostics/windows-version-control/README.md +++ b/scripts/diagnostics/windows-version-control/README.md @@ -10,6 +10,12 @@ authentication values, and an ephemeral loopback listener. It is configured with no real credentials and one unavailable loopback provider. Other network egress is not blocked or audited. +The controller captures the original home before replacing the child +environment and arms the test-home guard (`OCX_TEST_HOME_GUARD`, +`OCX_REAL_HOME`) with that protected identity. Rewriting fixture `HOME` and +`USERPROFILE` does not redefine the protected OpenCodex and Codex homes. +The module contracts exercise the same setup with a synthetic original home. + ```powershell cargo test --locked --manifest-path scripts/diagnostics/windows-version-control/Cargo.toml --test async_contracts -- --test-threads=4 cargo clippy --locked --manifest-path scripts/diagnostics/windows-version-control/Cargo.toml --all-targets -- -D warnings diff --git a/scripts/diagnostics/windows-version-control/src/control.rs b/scripts/diagnostics/windows-version-control/src/control.rs index 3de54930fac..e1003ffcd40 100644 --- a/scripts/diagnostics/windows-version-control/src/control.rs +++ b/scripts/diagnostics/windows-version-control/src/control.rs @@ -312,6 +312,12 @@ pub fn run() -> Result<(), Box> { { return Err("all paths must be absolute".into()); } + let original_home = env::var_os("OCX_REAL_HOME") + .or_else(|| env::var_os("USERPROFILE")) + .or_else(|| env::var_os("HOME")) + .map(PathBuf::from) + .filter(|path| path.is_absolute()) + .ok_or("missing absolute original home")?; fs::create_dir(&root)?; let home = root.join("opencodex"); let codex = root.join("codex"); @@ -346,8 +352,10 @@ pub fn run() -> Result<(), Box> { )?; let system = env::var_os("SystemRoot").ok_or("missing SystemRoot")?; let path = env::join_paths([bin.clone(), Path::new(&system).join("System32")])?; - let child = Command::new(&args[0]) - .env_clear() + let mut command = Command::new(&args[0]); + command.env_clear(); + crate::fixture_env::arm_test_home_guard(&mut command, &original_home)?; + let child = command .env("SystemRoot", &system) .env("WINDIR", &system) .env("PATH", path) diff --git a/scripts/diagnostics/windows-version-control/src/fixture_env.rs b/scripts/diagnostics/windows-version-control/src/fixture_env.rs new file mode 100644 index 00000000000..f89e4f70113 --- /dev/null +++ b/scripts/diagnostics/windows-version-control/src/fixture_env.rs @@ -0,0 +1,15 @@ +use std::{io, path::Path, process::Command}; + +/// Preserve the guard's protected home across fixture environment replacement. +pub fn arm_test_home_guard(command: &mut Command, original_home: &Path) -> io::Result<()> { + if !original_home.is_absolute() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "the original home must be absolute", + )); + } + command + .env("OCX_TEST_HOME_GUARD", "1") + .env("OCX_REAL_HOME", original_home); + Ok(()) +} diff --git a/scripts/diagnostics/windows-version-control/src/main.rs b/scripts/diagnostics/windows-version-control/src/main.rs index 0a81e140e32..a6f95662caf 100644 --- a/scripts/diagnostics/windows-version-control/src/main.rs +++ b/scripts/diagnostics/windows-version-control/src/main.rs @@ -2,6 +2,8 @@ #[cfg(windows)] mod control; +#[cfg(windows)] +mod fixture_env; fn main() { #[cfg(windows)] diff --git a/scripts/diagnostics/windows-version-control/tests/async_contracts.test.rs b/scripts/diagnostics/windows-version-control/tests/async_contracts.test.rs index 8d93c12eb85..f7edded76da 100644 --- a/scripts/diagnostics/windows-version-control/tests/async_contracts.test.rs +++ b/scripts/diagnostics/windows-version-control/tests/async_contracts.test.rs @@ -1,6 +1,9 @@ use serde_json::Value; use std::{env, fs, path::PathBuf, process::Command}; +#[path = "../src/fixture_env.rs"] +mod fixture_env; + #[test] fn eager_eof_releases_upstream_abort_listeners_and_reader() { let result = contract( @@ -251,6 +254,7 @@ console.log('@@catalog-contract@@'+JSON.stringify(answer)); ); let mut command = hidden(Command::new(bun)); command.env_clear(); + fixture_env::arm_test_home_guard(&mut command, &root.join("original-home")).unwrap(); if let Some(system) = env::var_os("SystemRoot") { command.env("SystemRoot", &system).env("WINDIR", &system); } @@ -625,3 +629,38 @@ return answers; assert_eq!(answer["reaped"], true); } } + +#[test] +fn home_guard_survives_fixture_home_replacement() { + let result = contract( + "home-guard", + r#" +const guard = await import(pathToFileURL(join(process.env.CONTRACT_REPO, 'src/lib/test-home-guard.ts')).href); +const original = process.env.OCX_REAL_HOME; +let homeRefused = false, codexRefused = false; +try { guard.assertNotRealHomeUnderTest(join(original, '.opencodex')); } catch { homeRefused = true; } +try { guard.assertNotRealCodexHomeUnderTest(join(original, '.codex')); } catch { codexRefused = true; } +guard.assertNotRealHomeUnderTest(root); +guard.assertNotRealCodexHomeUnderTest(root); +return { + armed: process.env.OCX_TEST_HOME_GUARD === '1', + rewritten: process.env.HOME !== original && process.env.USERPROFILE !== original, + homeRefused, codexRefused, fixtureAllowed: true +}; +"#, + ); + assert_eq!(result["armed"], true); + assert_eq!(result["rewritten"], true); + assert_eq!(result["homeRefused"], true); + assert_eq!(result["codexRefused"], true); + assert_eq!(result["fixtureAllowed"], true); +} + +#[test] +fn home_guard_requires_an_absolute_protected_home() { + let mut command = Command::new("unused-fixture"); + let error = + fixture_env::arm_test_home_guard(&mut command, std::path::Path::new("relative-home")) + .unwrap_err(); + assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput); +}