fix(git): bind configuration environment

This commit is contained in:
Chris Bookholt
2026-07-03 18:29:53 -07:00
parent 6b40c77a0f
commit 21ca43303b
6 changed files with 372 additions and 1 deletions
@@ -52,6 +52,95 @@ fn run_git_stdout(cwd: &Path, args: &[&str]) -> String {
.to_string()
}
#[test]
fn runner_binds_config_environment_across_ambient_mutation() {
const CHILD: &str = "CODEX_GIT_CONFIG_BINDING_CHILD";
if std::env::var_os(CHILD).is_none() {
let fixture = tempfile::tempdir().expect("fixture");
let repo = fixture.path().join("repo");
let safe_home = fixture.path().join("safe-home");
let malicious_home = repo.join("malicious-home");
std::fs::create_dir_all(&repo).expect("repo");
std::fs::create_dir_all(&safe_home).expect("safe home");
std::fs::create_dir_all(&malicious_home).expect("malicious home");
run_git(&repo, &["init", "-q"]);
std::fs::write(
safe_home.join(".gitconfig"),
"[codex]\n\tbound = safe-home\n",
)
.expect("safe home config");
let malicious_global = repo.join("malicious.gitconfig");
std::fs::write(&malicious_global, "[codex]\n\tbound = malicious\n")
.expect("malicious global config");
std::fs::write(
malicious_home.join(".gitconfig"),
"[codex]\n\tbound = malicious-home\n",
)
.expect("malicious home config");
let mut command = Command::new(std::env::current_exe().expect("test executable"));
isolate_git_command_environment(&mut command);
let output = command
.arg("git_command::tests::runner_binds_config_environment_across_ambient_mutation")
.arg("--exact")
.arg("--nocapture")
.env(CHILD, "1")
.env("RUST_TEST_THREADS", "1")
.env("CODEX_GIT_CONFIG_BINDING_ROOT", &repo)
.env("CODEX_GIT_CONFIG_BINDING_MALICIOUS", &malicious_global)
.env("CODEX_GIT_CONFIG_BINDING_MALICIOUS_HOME", &malicious_home)
.env("HOME", &safe_home)
.env("GIT_CONFIG_NOSYSTEM", "1")
.env("GIT_CONFIG_COUNT", "1")
.env("GIT_CONFIG_KEY_0", "codex.count")
.env("GIT_CONFIG_VALUE_0", "safe-count")
.env_remove("GIT_CONFIG_GLOBAL")
.env_remove("GIT_CONFIG_SYSTEM")
.env_remove("GIT_CONFIG_PARAMETERS")
.output()
.expect("isolated config binding test");
assert!(
output.status.success(),
"isolated config binding test failed:\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
return;
}
let root = PathBuf::from(std::env::var_os("CODEX_GIT_CONFIG_BINDING_ROOT").expect("root"));
let malicious =
std::env::var_os("CODEX_GIT_CONFIG_BINDING_MALICIOUS").expect("malicious config");
let malicious_home =
std::env::var_os("CODEX_GIT_CONFIG_BINDING_MALICIOUS_HOME").expect("malicious home");
let runner = GitRunner::for_cwd_io(&root).expect("runner with captured config environment");
// SAFETY: the parent starts an isolated test process that runs only this
// exact test with one harness thread. No other application thread reads or
// writes these variables, and the process exits after the assertions.
unsafe {
std::env::set_var("GIT_CONFIG_GLOBAL", malicious);
std::env::set_var("HOME", malicious_home);
std::env::set_var("GIT_CONFIG_VALUE_0", "malicious-count");
std::env::set_var(
"GIT_CONFIG_PARAMETERS",
"'codex.count'='malicious-parameter'",
);
}
for (key, expected) in [("codex.bound", "safe-home"), ("codex.count", "safe-count")] {
let mut command = runner.command_for_cwd(&root).expect("bound command");
command.args(["config", "--get", key]);
let output = runner.output(command).expect("bound config read");
assert!(
output.status.success(),
"{key}: {}",
String::from_utf8_lossy(&output.stderr)
);
assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), expected);
}
}
fn commit_all(cwd: &Path, message: &str) {
run_git(
cwd,
@@ -396,6 +485,12 @@ fn git_read_error_io_kind_table_is_exhaustive() {
},
io::ErrorKind::InvalidData,
),
(
GitReadError::InvalidConfigEnvironment {
reason: "malformed GIT_CONFIG_COUNT".to_string(),
},
io::ErrorKind::InvalidData,
),
] {
assert_eq!(error.io_kind(), expected, "{error}");
assert_eq!(error.into_io_error().kind(), expected);