diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000..75a56c0 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2025-02-27 - Path Traversal Vulnerability in Manual Path Normalization +**Vulnerability:** The `typescript` extractor manual path normalization (used when `canonicalize()` fails) suffered from a path traversal flaw. When reducing `../` (ParentDir) segments by popping from a vector of path components, the `pop()` method returned silently if the vector was empty. Consequently, paths starting with `../` lost those parent directory components, incorrectly anchoring relative references locally or resulting in unintended, out-of-bounds filesystem access attempts outside the original root directory. +**Learning:** Manual path component normalization utilizing vector reductions must account for starting segments (`../`) and constraints (Root or Prefix directives). Merely popping elements fails for valid, relative paths that start with multiple `../` components, collapsing paths into unintended domains. +**Prevention:** Always push `ParentDir` if the stack is empty or currently terminating in a `ParentDir`, and drop it if constrained by root scopes. Never blindly pop elements when reducing relative path directives. diff --git a/crates/flow/src/incremental/extractors/typescript.rs b/crates/flow/src/incremental/extractors/typescript.rs index 1bdda4e..b3a23c7 100644 --- a/crates/flow/src/incremental/extractors/typescript.rs +++ b/crates/flow/src/incremental/extractors/typescript.rs @@ -807,9 +807,16 @@ impl TypeScriptDependencyExtractor { let mut components = Vec::new(); for component in resolved.components() { match component { - std::path::Component::ParentDir => { - components.pop(); - } + std::path::Component::ParentDir => match components.last() { + Some(std::path::Component::ParentDir) | None => { + components.push(component) + } + Some(std::path::Component::RootDir) + | Some(std::path::Component::Prefix(_)) => {} + _ => { + components.pop(); + } + }, std::path::Component::CurDir => {} _ => components.push(component), }