refuse a diff that makes agents.md anything but a regular file
This commit is contained in:
parent
9add99838c
commit
e59c01059a
1 changed files with 69 additions and 0 deletions
|
|
@ -204,6 +204,13 @@ fn apply_diff(state_dir: &Path, apply: &Apply) -> ApplyReply {
|
|||
if at != apply.base_revision {
|
||||
return ApplyReply::Stale { revision: at };
|
||||
}
|
||||
if let Some(mode) = non_regular_mode(&apply.diff) {
|
||||
return ApplyReply::Rejected {
|
||||
error: format!(
|
||||
"the diff sets file mode {mode}; AGENTS.md must stay a regular file (100644)"
|
||||
),
|
||||
};
|
||||
}
|
||||
let work = match tempfile::tempdir() {
|
||||
Ok(dir) => dir,
|
||||
Err(e) => return failed("creating a directory to apply in", &e),
|
||||
|
|
@ -234,6 +241,12 @@ fn apply_diff(state_dir: &Path, apply: &Apply) -> ApplyReply {
|
|||
error: format!("the diff must change AGENTS.md and nothing else; it left {left:?}"),
|
||||
};
|
||||
}
|
||||
// Belt and braces for the mode check above: never read through a link.
|
||||
if !std::fs::symlink_metadata(©).is_ok_and(|m| m.file_type().is_file()) {
|
||||
return ApplyReply::Rejected {
|
||||
error: "the diff left AGENTS.md as something other than a regular file".to_owned(),
|
||||
};
|
||||
}
|
||||
let edited = match std::fs::read(©) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(e) => return failed("reading the applied AGENTS.md", &e),
|
||||
|
|
@ -248,8 +261,20 @@ fn apply_diff(state_dir: &Path, apply: &Apply) -> ApplyReply {
|
|||
|
||||
/// Run `git <args>` in `dir` with `diff` on stdin. The outer error is git not
|
||||
/// running; the inner one is git's stderr when it exits non-zero.
|
||||
/// The first file mode a diff header sets that is not `100644`, if any.
|
||||
fn non_regular_mode(diff: &str) -> Option<&str> {
|
||||
diff.lines()
|
||||
.filter_map(|l| {
|
||||
l.strip_prefix("new file mode ")
|
||||
.or_else(|| l.strip_prefix("new mode "))
|
||||
})
|
||||
.map(str::trim)
|
||||
.find(|m| *m != "100644")
|
||||
}
|
||||
|
||||
fn git(dir: &Path, args: &[&str], diff: &str) -> std::io::Result<Result<(), String>> {
|
||||
let mut child = std::process::Command::new("git")
|
||||
.args(["-c", "core.symlinks=false"])
|
||||
.args(args)
|
||||
.current_dir(dir)
|
||||
// Outside a repository `git apply` patches plain files, as `patch`
|
||||
|
|
@ -396,6 +421,50 @@ mod tests {
|
|||
assert_eq!(file(&dir), "# atlas\n");
|
||||
}
|
||||
|
||||
/// A symlink diff must not make the harness read the link's target into
|
||||
/// AGENTS.md, which the mirror then publishes.
|
||||
#[test]
|
||||
fn a_diff_that_creates_a_symlink_is_refused_and_nothing_is_read_through_it() {
|
||||
let secret = tempfile::tempdir().expect("tempdir");
|
||||
let target = secret.path().join("secret");
|
||||
std::fs::write(&target, "do not publish\n").expect("write");
|
||||
let link = format!(
|
||||
"diff --git a/AGENTS.md b/AGENTS.md\nnew file mode 120000\n--- /dev/null\n+++ b/AGENTS.md\n@@ -0,0 +1 @@\n+{}\n\\ No newline at end of file\n",
|
||||
target.display()
|
||||
);
|
||||
|
||||
// Fresh create.
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let reply = apply_diff(dir.path(), &apply("", &link));
|
||||
assert!(matches!(reply, ApplyReply::Rejected { .. }), "{reply:?}");
|
||||
assert!(!dir.path().join("AGENTS.md").exists());
|
||||
|
||||
// Typechange of an existing file.
|
||||
let dir = state(BEFORE);
|
||||
let typechange = format!(
|
||||
"diff --git a/AGENTS.md b/AGENTS.md\ndeleted file mode 100644\n--- a/AGENTS.md\n+++ /dev/null\n@@ -1,2 +0,0 @@\n-# atlas\n-keep notes short\n{link}"
|
||||
);
|
||||
let reply = apply_diff(dir.path(), &apply(BEFORE, &typechange));
|
||||
assert!(matches!(reply, ApplyReply::Rejected { .. }), "{reply:?}");
|
||||
assert_eq!(file(&dir), BEFORE);
|
||||
}
|
||||
|
||||
/// The mode check is the first layer; with it out of the way git must
|
||||
/// still not write a link (`core.symlinks=false`) and the regular-file
|
||||
/// check must hold.
|
||||
#[test]
|
||||
fn a_link_never_survives_the_apply_even_without_the_mode_check() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let work = tempfile::tempdir().expect("tempdir");
|
||||
let diff = "diff --git a/AGENTS.md b/AGENTS.md\nnew file mode 120000\n--- /dev/null\n+++ b/AGENTS.md\n@@ -0,0 +1 @@\n+/etc/hostname\n\\ No newline at end of file\n";
|
||||
super::git(work.path(), &["apply"], diff)
|
||||
.expect("git runs")
|
||||
.expect("applies");
|
||||
let meta = std::fs::symlink_metadata(work.path().join("AGENTS.md")).expect("exists");
|
||||
assert!(meta.file_type().is_file(), "git wrote a link");
|
||||
drop(dir);
|
||||
}
|
||||
|
||||
/// The next mirror write, or `None` after ten seconds without one.
|
||||
async fn next(rx: &mut tokio::sync::mpsc::UnboundedReceiver<Mirror>) -> Option<Mirror> {
|
||||
tokio::time::timeout(Duration::from_secs(10), rx.recv())
|
||||
|
|
|
|||
Loading…
Reference in a new issue