diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 00000000..92c50d3e --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,5 @@ + +## 2024-05-20 - Lexical Path Normalization Traversal Vulnerability +**Vulnerability:** In `crates/flow/src/incremental/extractors/typescript.rs`, manual path normalization handled `..` components by blindly calling `components.pop()`. +**Learning:** If `components` was empty or contained boundary components like `/` (`RootDir`), popping did nothing, effectively deleting `..` directories and breaking relative navigation. Alternatively, `components.pop()` would happily strip root indicators, allowing path ascensions to escape intended root scopes. +**Prevention:** When manually normalizing paths using `std::path::Component`, explicitly block `ParentDir` from popping `RootDir` or `Prefix`. If the components stack is empty or its top is already `ParentDir`, push the new `ParentDir` to preserve valid ascensions (e.g., `../../file`). diff --git a/crates/flow/src/incremental/extractors/typescript.rs b/crates/flow/src/incremental/extractors/typescript.rs index 1bdda4ef..2a44eaef 100644 --- a/crates/flow/src/incremental/extractors/typescript.rs +++ b/crates/flow/src/incremental/extractors/typescript.rs @@ -808,7 +808,17 @@ impl TypeScriptDependencyExtractor { for component in resolved.components() { match component { std::path::Component::ParentDir => { - components.pop(); + let last = components.last().copied(); + match last { + None | Some(std::path::Component::ParentDir) => { + components.push(component); + } + Some(std::path::Component::RootDir) + | Some(std::path::Component::Prefix(_)) => {} + _ => { + components.pop(); + } + } } std::path::Component::CurDir => {} _ => components.push(component),