mirror of
https://github.com/openai/codex.git
synced 2026-10-21 17:48:44 +00:00
git-utils: reject Git from enclosing repositories
This commit is contained in:
@@ -2,6 +2,8 @@ use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::process::Command;
|
||||
|
||||
use crate::safe_git::DISABLED_HOOKS_PATH;
|
||||
|
||||
#[cfg(unix)]
|
||||
fn write_executable(path: &Path, body: &str) {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
@@ -18,6 +20,12 @@ fn run_git(cwd: &Path, args: &[&str]) {
|
||||
let mut command = Command::new("git");
|
||||
isolate_git_command_environment(&mut command);
|
||||
let status = command
|
||||
.args([
|
||||
"-c",
|
||||
&format!("core.hooksPath={DISABLED_HOOKS_PATH}"),
|
||||
"-c",
|
||||
"core.fsmonitor=false",
|
||||
])
|
||||
.args(args)
|
||||
.current_dir(cwd)
|
||||
.status()
|
||||
@@ -25,6 +33,23 @@ fn run_git(cwd: &Path, args: &[&str]) {
|
||||
assert!(status.success(), "git {args:?} failed");
|
||||
}
|
||||
|
||||
fn commit_all(cwd: &Path, message: &str) {
|
||||
run_git(
|
||||
cwd,
|
||||
&[
|
||||
"-c",
|
||||
"user.name=Codex Test",
|
||||
"-c",
|
||||
"user.email=codex@example.com",
|
||||
"-c",
|
||||
"commit.gpgSign=false",
|
||||
"commit",
|
||||
"-qam",
|
||||
message,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
fn write_git_candidate(directory: &Path) {
|
||||
std::fs::create_dir_all(directory).expect("create candidate directory");
|
||||
let candidate = directory.join(git_executable_name());
|
||||
@@ -39,7 +64,7 @@ fn write_git_candidate(directory: &Path) {
|
||||
fn locations_for_root(root: &Path) -> UntrustedGitLocations {
|
||||
UntrustedGitLocations {
|
||||
roots: vec![std::fs::canonicalize(root).expect("canonical root")],
|
||||
common_dir: None,
|
||||
common_dirs: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +132,130 @@ fn linked_worktree_rejects_git_from_main_and_linked_worktrees() {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_repository_rejects_git_from_enclosing_repository() {
|
||||
let fixture = tempfile::tempdir().expect("fixture");
|
||||
let outer = fixture.path().join("outer");
|
||||
let nested = outer.join("nested");
|
||||
let outer_bin = outer.join("bin");
|
||||
let trusted_bin = fixture.path().join("trusted-bin");
|
||||
std::fs::create_dir_all(&nested).expect("create nested repository");
|
||||
run_git(&outer, &["init", "-q"]);
|
||||
run_git(&nested, &["init", "-q"]);
|
||||
write_git_candidate(&outer_bin);
|
||||
write_git_candidate(&trusted_bin);
|
||||
|
||||
let locations = untrusted_git_locations_for_cwd(&nested).expect("untrusted locations");
|
||||
assert!(
|
||||
path_is_untrusted(&outer_bin.join(git_executable_name()), &locations),
|
||||
"Git from an enclosing repository must remain repository-controlled"
|
||||
);
|
||||
assert_eq!(
|
||||
selected_git(&locations, &[&outer_bin, &trusted_bin]),
|
||||
trusted_bin.join(git_executable_name())
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn symlinked_nested_repository_rejects_git_from_lexical_enclosing_repository() {
|
||||
let fixture = tempfile::tempdir().expect("fixture");
|
||||
let outer = fixture.path().join("outer");
|
||||
let physical_nested = fixture.path().join("physical-nested");
|
||||
let lexical_nested = outer.join("nested");
|
||||
let outer_bin = outer.join("bin");
|
||||
let trusted_bin = fixture.path().join("trusted-bin");
|
||||
std::fs::create_dir_all(&outer).expect("create outer repository");
|
||||
std::fs::create_dir_all(&physical_nested).expect("create physical nested repository");
|
||||
run_git(&outer, &["init", "-q"]);
|
||||
run_git(&physical_nested, &["init", "-q"]);
|
||||
std::os::unix::fs::symlink(&physical_nested, &lexical_nested)
|
||||
.expect("symlink nested repository");
|
||||
write_git_candidate(&outer_bin);
|
||||
write_git_candidate(&trusted_bin);
|
||||
|
||||
let locations = untrusted_git_locations_for_cwd(&lexical_nested).expect("untrusted locations");
|
||||
assert!(
|
||||
path_is_untrusted(&outer_bin.join(git_executable_name()), &locations),
|
||||
"Git from the lexical enclosing repository must remain repository-controlled"
|
||||
);
|
||||
assert_eq!(
|
||||
selected_git(&locations, &[&outer_bin, &trusted_bin]),
|
||||
trusted_bin.join(git_executable_name())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_repository_rejects_git_from_enclosing_repository_main_worktree() {
|
||||
let fixture = tempfile::tempdir().expect("fixture");
|
||||
let main = fixture.path().join("main");
|
||||
let linked = fixture.path().join("linked");
|
||||
let nested = linked.join("nested");
|
||||
let main_bin = main.join("bin");
|
||||
let trusted_bin = fixture.path().join("trusted-bin");
|
||||
std::fs::create_dir_all(&main).expect("create main worktree");
|
||||
run_git(&main, &["init", "-q"]);
|
||||
run_git(&main, &["worktree", "add", "--orphan", path_text(&linked)]);
|
||||
std::fs::create_dir_all(&nested).expect("create nested repository");
|
||||
run_git(&nested, &["init", "-q"]);
|
||||
write_git_candidate(&main_bin);
|
||||
write_git_candidate(&trusted_bin);
|
||||
|
||||
let locations = untrusted_git_locations_for_cwd(&nested).expect("untrusted locations");
|
||||
assert!(
|
||||
path_is_untrusted(&main_bin.join(git_executable_name()), &locations),
|
||||
"all worktrees of an enclosing repository must remain repository-controlled"
|
||||
);
|
||||
assert_eq!(
|
||||
selected_git(&locations, &[&main_bin, &trusted_bin]),
|
||||
trusted_bin.join(git_executable_name())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submodule_rejects_git_from_enclosing_superproject() {
|
||||
let fixture = tempfile::tempdir().expect("fixture");
|
||||
let source = fixture.path().join("source");
|
||||
let outer = fixture.path().join("outer");
|
||||
let submodule = outer.join("nested");
|
||||
let outer_bin = outer.join("bin");
|
||||
let trusted_bin = fixture.path().join("trusted-bin");
|
||||
std::fs::create_dir_all(&source).expect("create source repository");
|
||||
std::fs::create_dir_all(&outer).expect("create superproject");
|
||||
run_git(&source, &["init", "-q"]);
|
||||
std::fs::write(source.join("source.txt"), "source\n").expect("write source file");
|
||||
run_git(&source, &["add", "source.txt"]);
|
||||
commit_all(&source, "source");
|
||||
run_git(&outer, &["init", "-q"]);
|
||||
std::fs::write(outer.join("outer.txt"), "outer\n").expect("write outer file");
|
||||
run_git(&outer, &["add", "outer.txt"]);
|
||||
commit_all(&outer, "outer");
|
||||
run_git(
|
||||
&outer,
|
||||
&[
|
||||
"-c",
|
||||
"protocol.file.allow=always",
|
||||
"submodule",
|
||||
"add",
|
||||
"-q",
|
||||
path_text(&source),
|
||||
"nested",
|
||||
],
|
||||
);
|
||||
write_git_candidate(&outer_bin);
|
||||
write_git_candidate(&trusted_bin);
|
||||
|
||||
let locations = untrusted_git_locations_for_cwd(&submodule).expect("untrusted locations");
|
||||
assert!(
|
||||
path_is_untrusted(&outer_bin.join(git_executable_name()), &locations),
|
||||
"Git from a superproject must remain repository-controlled"
|
||||
);
|
||||
assert_eq!(
|
||||
selected_git(&locations, &[&outer_bin, &trusted_bin]),
|
||||
trusted_bin.join(git_executable_name())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bare_backed_linked_worktree_allows_external_git_in_sibling_directory() {
|
||||
let fixture = tempfile::tempdir().expect("fixture");
|
||||
@@ -171,6 +320,23 @@ fn separate_dot_git_dir_rejects_main_candidate_and_allows_unrelated_repo_candida
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolver_rejects_parent_traversal_spelled_through_repository() {
|
||||
let fixture = tempfile::tempdir().expect("fixture");
|
||||
let repo = fixture.path().join("repo");
|
||||
let trusted_bin = fixture.path().join("trusted-bin");
|
||||
std::fs::create_dir_all(&repo).expect("create repository");
|
||||
write_git_candidate(&trusted_bin);
|
||||
|
||||
let locations = locations_for_root(&repo);
|
||||
let traversing_path = locations.roots[0].join("../trusted-bin");
|
||||
let search_path = std::env::join_paths([traversing_path]).expect("PATH");
|
||||
assert!(matches!(
|
||||
GitRunner::from_search_path(&locations, &search_path),
|
||||
Err(GitReadError::NoTrustedGit)
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn resolver_selects_native_git_exe_only() {
|
||||
|
||||
Reference in New Issue
Block a user