fix/audit-findings #5

Merged
trevon merged 14 commits from fix/audit-findings into main 2026-09-25 02:39:30 +00:00
Showing only changes of commit 4b2c873445 - Show all commits

fix(fold): follow the filesystem for .. after a symlinked directory

Fold report paths were normalized lexically, so `linkout/../src/main.rs`
with `linkout` pointing outside the root attached to the repository's
`src/main.rs` although the OS path names a different file.

A relative path containing `..` that exists in this checkout is now
canonicalized (and must stay under the canonical root); paths absent
from the checkout, such as reports from another machine, keep the
documented lexical matching.

Test: fold::tests::parent_steps_after_a_symlinked_directory_follow_the_filesystem
fails before this change (the finding attaches to src/main.rs).
trvon 2026-09-24 20:12:13 -06:00
Signed by: trevon
GPG key ID: 38686EA797D7A8A7

View file

@ -372,7 +372,24 @@ fn find_file(building: &Building, path: &str) -> Option<String> {
{
return None;
}
normalized
// When the path exists here, follow the filesystem: `link/../x` names what
// the OS resolves through `link`, not the lexical `x`. Paths absent from this
// checkout (a report from another machine) keep lexical matching.
let on_disk = Path::new(&building.root).join(path.replace('\\', "/"));
if path.contains("..")
&& let (Ok(canonical), Ok(root)) = (
on_disk.canonicalize(),
Path::new(&building.root).canonicalize(),
)
{
canonical
.strip_prefix(&root)
.ok()?
.to_str()?
.replace('\\', "/")
} else {
normalized
}
};
building
.nodes
@ -512,7 +529,10 @@ mod tests {
})).collect();
fs::write(
&report,
serde_json::to_vec(&serde_json::json!({"tool": {"name": "fold"}, "findings": findings})).unwrap(),
serde_json::to_vec(
&serde_json::json!({"tool": {"name": "fold"}, "findings": findings}),
)
.unwrap(),
)
.unwrap();
let mut model = scan(&root).unwrap();
@ -822,4 +842,43 @@ mod tests {
building.diagnostics
);
}
#[cfg(unix)]
#[test]
fn parent_steps_after_a_symlinked_directory_follow_the_filesystem() {
// `linkout/../src/main.rs` with `linkout` pointing outside the root was
// normalized lexically to `src/main.rs`, a file the OS path does not name.
use std::os::unix::fs::symlink;
let workspace = tempfile::tempdir().unwrap();
let root = workspace.path().join("repo");
fs::create_dir_all(root.join("src")).unwrap();
fs::write(root.join("src/main.rs"), "fn main() {}\n").unwrap();
let outside = workspace.path().join("elsewhere/deeper");
fs::create_dir_all(&outside).unwrap();
fs::create_dir_all(workspace.path().join("elsewhere/src")).unwrap();
fs::write(
workspace.path().join("elsewhere/src/main.rs"),
"fn other() {}\n",
)
.unwrap();
symlink(&outside, root.join("linkout")).unwrap();
let report = workspace.path().join("fold.json");
let value = serde_json::json!({
"tool": {"name": "fold"},
"findings": [{
"id": "through-link", "kind": "test",
"locations": [{"file": "linkout/../src/main.rs", "line": 1}],
"confidence": "heuristic", "evidence": {"backend": "source"}
}]
});
fs::write(&report, serde_json::to_vec(&value).unwrap()).unwrap();
let mut model = scan(&root).unwrap();
assert_eq!(import_findings(&mut model, &report).unwrap(), 0);
assert!(
model
.diagnostics
.iter()
.any(|d| d.message.contains("did not match a scanned file"))
);
}
}