From 21dc98857df226029f1f7e74bda7e9d5b7d684a9 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 25 May 2026 17:32:53 +0000 Subject: [PATCH] Fix path traversal vulnerability in typescript extractor path resolution This commit patches the manual path resolution logic inside `crates/flow/src/incremental/extractors/typescript.rs`. Previously, it naively called `components.pop()` when encountering `std::path::Component::ParentDir`. This could allow popping the `RootDir` or `Prefix`, turning an absolute path into a relative one or escaping directory bounds. It also failed to correctly accumulate `..` components for paths like `../../a`. The new logic checks `components.last()` and prevents popping the root/prefix, and properly pushes `ParentDir` when appropriate to maintain safe and correct path resolution. Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com> --- .jules/sentinel.md | 5 +++++ .../flow/src/incremental/extractors/typescript.rs | 15 ++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 .jules/sentinel.md diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 00000000..f81a465a --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,5 @@ + +## 2024-05-25 - Path Traversal in TypeScript Extractor +**Vulnerability:** The manual path resolution logic in `crates/flow/src/incremental/extractors/typescript.rs` naively popped the last component when encountering `..` (`std::path::Component::ParentDir`). This allowed escaping the root directory by popping `RootDir` or `Prefix`, and failed to handle relative paths like `../../a` correctly. +**Learning:** `std::path::Path::components()` and `Vec::pop()` are not sufficient for secure path normalization. Popping `RootDir` effectively makes an absolute path relative, and popping an empty list discards `..` segments that might be necessary for relative traversal. +**Prevention:** Always check `components.last()` before popping. Prevent popping if the last component is `RootDir` or `Prefix`. If the list is empty or the last component is already `ParentDir`, push the new `ParentDir` to preserve correct relative path structure. diff --git a/crates/flow/src/incremental/extractors/typescript.rs b/crates/flow/src/incremental/extractors/typescript.rs index 1bdda4ef..95898957 100644 --- a/crates/flow/src/incremental/extractors/typescript.rs +++ b/crates/flow/src/incremental/extractors/typescript.rs @@ -808,7 +808,20 @@ impl TypeScriptDependencyExtractor { for component in resolved.components() { match component { std::path::Component::ParentDir => { - components.pop(); + if let Some(last) = components.last() { + if matches!( + last, + std::path::Component::RootDir | std::path::Component::Prefix(_) + ) { + // Security: prevent popping root or prefix + } else if matches!(last, std::path::Component::ParentDir) { + components.push(component); + } else { + components.pop(); + } + } else { + components.push(component); + } } std::path::Component::CurDir => {} _ => components.push(component),