diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000..4de062f --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2025-02-28 - [Path Traversal bypass in PathBuf resolution] +**Vulnerability:** Found a vulnerable path component normalization in `crates/flow/src/incremental/extractors/typescript.rs`. Manual handling of `..` (`ParentDir`) blindly called `components.pop()`. This fails to handle root directories or prefixes correctly (converting absolute paths to relative, allowing traversal escapes), and incorrectly ignores `..` at the beginning of relative paths (e.g., resolving `../../a` to `a`). +**Learning:** `std::path::PathBuf::components()` correctly yields `Component::RootDir` and `Component::ParentDir`. Normalization algorithms MUST explicitly check the previous component before popping. Popping `RootDir` is inherently unsafe as it changes path type. Empty states or states ending in `ParentDir` must push `ParentDir` instead of doing nothing or blindly popping. +**Prevention:** Whenever manual path resolution loop involves popping on `Component::ParentDir`, check the top of the component stack first to prevent popping prefixes/roots or ignoring `ParentDir` when the stack is empty/only contains `ParentDir`. diff --git a/crates/flow/src/incremental/extractors/typescript.rs b/crates/flow/src/incremental/extractors/typescript.rs index 1bdda4e..da3ab99 100644 --- a/crates/flow/src/incremental/extractors/typescript.rs +++ b/crates/flow/src/incremental/extractors/typescript.rs @@ -808,7 +808,21 @@ impl TypeScriptDependencyExtractor { for component in resolved.components() { match component { std::path::Component::ParentDir => { - components.pop(); + let pop = match components.last() { + Some(std::path::Component::RootDir) + | Some(std::path::Component::Prefix(_)) => false, + None | Some(std::path::Component::ParentDir) => false, + _ => true, + }; + if pop { + components.pop(); + } else if !matches!( + components.last(), + Some(std::path::Component::RootDir) + | Some(std::path::Component::Prefix(_)) + ) { + components.push(component); + } } std::path::Component::CurDir => {} _ => components.push(component),