From 2dd5d79f36568b2ad4b224fcbac41dc9af3e6089 Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Fri, 19 Jun 2026 23:06:48 -0400 Subject: [PATCH] refactor(server): fold in PM review notes on classify_path - classify_path now Rejects a collection slug containing '.' (mirrors OrgCollections::validate, plan L317, and item_path's documented contract, plan L990). Unreachable today since git normalizes './' away, but keeps the pre-receive hook self-defensive against path traversal. - Rename test item_write_nested_slug_takes_leading_segment_only -> item_write_nested_slug_is_rejected (it asserts Rejected; old name misled). - Add dotted_slug_is_rejected covering the new guard. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01M5brcDrT35r5GaJySXD5ja --- crates/relicario-server/src/lib.rs | 7 +++++++ crates/relicario-server/tests/org_hook.rs | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/crates/relicario-server/src/lib.rs b/crates/relicario-server/src/lib.rs index d2caa0b..97148a7 100644 --- a/crates/relicario-server/src/lib.rs +++ b/crates/relicario-server/src/lib.rs @@ -32,6 +32,13 @@ pub fn classify_path(path: &str) -> PathClass { if slug.is_empty() { return PathClass::Rejected("empty collection slug in items path".to_string()); } + // Defense-in-depth: mirror `OrgCollections::validate` — a slug containing + // '.' (e.g. a `..`/`.` path-traversal attempt) is structurally invalid. + // git normalizes most `./` away before the hook sees the path, so this is + // unreachable today; it keeps the hook self-defensive regardless. + if slug.contains('.') { + return PathClass::Rejected(format!("invalid collection slug: {:?}", slug)); + } return PathClass::Item { collection: slug.to_string() }; } diff --git a/crates/relicario-server/tests/org_hook.rs b/crates/relicario-server/tests/org_hook.rs index 5b6ebf0..7c306bf 100644 --- a/crates/relicario-server/tests/org_hook.rs +++ b/crates/relicario-server/tests/org_hook.rs @@ -18,8 +18,8 @@ fn item_write_yields_collection_slug() { } #[test] -fn item_write_nested_slug_takes_leading_segment_only() { - // Slugs cannot contain '/', so a 4-segment path is malformed → Rejected. +fn item_write_nested_slug_is_rejected() { + // Slugs cannot contain '/', so a path with extra segments is malformed → Rejected. assert_eq!( classify_path("items/prod/sub/x.enc"), PathClass::Rejected("items path must be items//.enc".to_string()) @@ -51,6 +51,16 @@ fn empty_slug_segment_is_rejected() { ); } +#[test] +fn dotted_slug_is_rejected() { + // Defense-in-depth (mirrors OrgCollections::validate): a slug containing '.' + // — e.g. a ".."/"." path-traversal attempt — is rejected. + assert_eq!( + classify_path("items/../x.enc"), + PathClass::Rejected("invalid collection slug: \"..\"".to_string()) + ); +} + use relicario_server::extract_schema_version; #[test]