From ea0523ad139ffb98f09683cec8ef9aefddd55bef Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 13 May 2026 18:03:36 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITICAL]?= =?UTF-8?q?=20Fix=20Path=20Traversal=20in=20TypeScript=20Module=20Path=20R?= =?UTF-8?q?esolution?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com> --- .jules/sentinel.md | 4 ++++ .../src/incremental/extractors/typescript.rs | 21 +++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 .jules/sentinel.md diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000..8c67394 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2025-02-27 - [CRITICAL] Fix Path Traversal in TypeScript Module Path Resolution +**Vulnerability:** The manual path resolution fallback in `TypeScriptDependencyExtractor` for canonicalization failures allowed dropping of root prefixes (`/`, `C:\`) and silent loss of excess `..` components when normalizing path traversals. +**Learning:** This occurred because the normalization loops simply popped elements off the `std::path::Component` vector when hitting `ParentDir`, blindly removing roots or emptying without correctly appending un-resolvable `ParentDir`s to represent true relative structures. +**Prevention:** Always maintain bound safety when manually normalizing paths using `std::path::Components`. Check explicitly for `Component::RootDir` and `Component::Prefix` when encountering a `ParentDir` to stop from erasing anchors. Similarly, if the component vector ends with a `ParentDir` or is empty, a new `ParentDir` must be explicitly pushed so relative structures don't break. diff --git a/crates/flow/src/incremental/extractors/typescript.rs b/crates/flow/src/incremental/extractors/typescript.rs index 1bdda4e..6643fc4 100644 --- a/crates/flow/src/incremental/extractors/typescript.rs +++ b/crates/flow/src/incremental/extractors/typescript.rs @@ -804,11 +804,28 @@ impl TypeScriptDependencyExtractor { resolved = canonical; } else { // If canonicalize fails (file doesn't exist), manually resolve - let mut components = Vec::new(); + let mut components: Vec = Vec::new(); for component in resolved.components() { match component { std::path::Component::ParentDir => { - components.pop(); + // Security fix: prevent path traversal attacks by explicitly blocking Component::ParentDir + // from popping Component::RootDir or Component::Prefix. + // If empty or already ends in ParentDir, push to preserve relative paths like ../../a + if let Some(last) = components.last() { + match last { + std::path::Component::RootDir | std::path::Component::Prefix(_) => { + // Cannot go above root/prefix, ignore ParentDir + } + std::path::Component::ParentDir => { + components.push(component); + } + _ => { + components.pop(); + } + } + } else { + components.push(component); + } } std::path::Component::CurDir => {} _ => components.push(component),