From 183a0b919b599d3eb5be2d75ff2c56b5b283067d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:29:50 +0000 Subject: [PATCH] Fix path traversal in TS module path resolution The `TypeScriptDependencyExtractor::resolve_module_path` manual fallback path normalization previously failed to correctly handle `ParentDir` (i.e. `..`) traversal limits, potentially allowing a path to erroneously escape its virtual root context or discard absolute prefixes incorrectly if it encountered consecutive `..` components. This update implements safer boundary checks. Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com> --- .jules/sentinel.md | 4 ++++ .../flow/src/incremental/extractors/typescript.rs | 13 ++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 .jules/sentinel.md diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000..d956618 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2025-05-15 - [Path Traversal in Manual Path Normalization] +**Vulnerability:** Manual path resolution using `components.pop()` on `std::path::Component::ParentDir` allowed path traversal. If a path like `../../a` was parsed, `components.pop()` on an empty `Vec` did nothing, turning the path into `a` instead of preserving the parent traversal. It could also pop `RootDir` or `Prefix` components, changing absolute paths to relative ones or traversing beyond intended roots. +**Learning:** `std::path::Component` normalization must handle empty lists and consecutive `ParentDir` components by pushing them instead of ignoring them. It must also explicitly avoid popping `RootDir` or `Prefix` components to prevent escaping virtual file systems or simulated root directories. +**Prevention:** Explicitly check if the `components` list is empty, if the last component is `ParentDir`, or if the last component is `RootDir` / `Prefix` before calling `pop()`. diff --git a/crates/flow/src/incremental/extractors/typescript.rs b/crates/flow/src/incremental/extractors/typescript.rs index 1bdda4e..52e22d9 100644 --- a/crates/flow/src/incremental/extractors/typescript.rs +++ b/crates/flow/src/incremental/extractors/typescript.rs @@ -808,7 +808,18 @@ impl TypeScriptDependencyExtractor { for component in resolved.components() { match component { std::path::Component::ParentDir => { - components.pop(); + let is_empty = components.is_empty(); + let last_is_parent = matches!(components.last(), Some(std::path::Component::ParentDir)); + let last_is_root_or_prefix = matches!( + components.last(), + Some(std::path::Component::RootDir) | Some(std::path::Component::Prefix(_)) + ); + + if is_empty || last_is_parent { + components.push(component); + } else if !last_is_root_or_prefix { + components.pop(); + } } std::path::Component::CurDir => {} _ => components.push(component),