From 736ca297876c1a760311009f3f43f4c8f67066e6 Mon Sep 17 00:00:00 2001 From: BryanFRD Date: Sat, 24 Oct 2026 21:25:09 +0200 Subject: [PATCH] fix(validate): reject directory traversal in packages[].path --- generator/src/validate.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/generator/src/validate.rs b/generator/src/validate.rs index 6928251..f811801 100644 --- a/generator/src/validate.rs +++ b/generator/src/validate.rs @@ -159,6 +159,12 @@ fn validate_single(path: &Path) -> std::result::Result, Vec> } for pkg in &def.packages { + if has_traversal(&pkg.path) { + errors.push(format!( + "packages['{}']: path '{}' contains directory traversal", + pkg.name, pkg.path + )); + } for file in &pkg.versioned_files { if has_traversal(&file.path) { errors.push(format!( @@ -318,6 +324,39 @@ mod tests { assert!(!validate_definitions(tmp.path()).unwrap()); } + #[test] + fn package_path_traversal() { + let tmp = TempDir::new().unwrap(); + write_def( + tmp.path(), + "traversal", + r#"{"meta":{"name":"t","description":"d"},"packages":[{"name":"a","path":"../outside","initial_version":"1.0.0"}]}"#, + ); + assert!(!validate_definitions(tmp.path()).unwrap()); + } + + #[test] + fn package_path_absolute() { + let tmp = TempDir::new().unwrap(); + write_def( + tmp.path(), + "absolute", + r#"{"meta":{"name":"t","description":"d"},"packages":[{"name":"a","path":"/etc","initial_version":"1.0.0"}]}"#, + ); + assert!(!validate_definitions(tmp.path()).unwrap()); + } + + #[test] + fn package_path_nested_ok() { + let tmp = TempDir::new().unwrap(); + write_def( + tmp.path(), + "ok", + r#"{"meta":{"name":"t","description":"d"},"packages":[{"name":"a","path":"packages/a","initial_version":"1.0.0"},{"name":"b","path":".","initial_version":"1.0.0"}]}"#, + ); + assert!(validate_definitions(tmp.path()).unwrap()); + } + #[test] fn commit_file_path_traversal() { let tmp = TempDir::new().unwrap();