From 31cb91afe41f0f6868de595a1b69291429675772 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 17:45:35 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITICAL]?= =?UTF-8?q?=20Fix=20path=20traversal=20in=20TS=20extractor?= 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 | 5 +++++ crates/flow/src/incremental/extractors/typescript.rs | 11 ++++++++--- crates/rule-engine/src/check_var.rs | 8 ++++---- 3 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 .jules/sentinel.md diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 00000000..fb16fa2b --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,5 @@ + +## 2024-05-22 - [Fix path traversal vulnerability in module resolution] +**Vulnerability:** The manual path resolution fallback in the TypeScript extractor popped path components unconditionally when encountering `..` (ParentDir), ignoring edge cases where `..` escapes the root directory or when leading `..` components should be preserved. +**Learning:** Manual path normalization logic must strictly follow semantic path construction and guard against unexpected component states like traversing above `RootDir` or losing relative paths like `../../foo`. +**Prevention:** Always validate the existing state of path components before modifying them during `ParentDir` handling, treating `RootDir` and `Prefix` as non-poppable, and preserving `ParentDir` when needed. diff --git a/crates/flow/src/incremental/extractors/typescript.rs b/crates/flow/src/incremental/extractors/typescript.rs index 1bdda4ef..3fe1aae6 100644 --- a/crates/flow/src/incremental/extractors/typescript.rs +++ b/crates/flow/src/incremental/extractors/typescript.rs @@ -807,9 +807,14 @@ 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::RootDir) + | Some(std::path::Component::Prefix(_)) => {} + Some(std::path::Component::Normal(_)) => { + components.pop(); + } + _ => components.push(component), + }, std::path::Component::CurDir => {} _ => components.push(component), } diff --git a/crates/rule-engine/src/check_var.rs b/crates/rule-engine/src/check_var.rs index 9e401055..17d6fa7f 100644 --- a/crates/rule-engine/src/check_var.rs +++ b/crates/rule-engine/src/check_var.rs @@ -104,9 +104,9 @@ fn get_vars_from_rules<'r>(rule: &'r Rule, utils: &'r RuleRegistration) -> Rapid vars } -fn check_var_in_constraints<'r>( +fn check_var_in_constraints( mut vars: RapidSet, - constraints: &'r RapidMap, + constraints: &RapidMap, ) -> RResult> { for rule in constraints.values() { for var in rule.defined_vars() { @@ -125,9 +125,9 @@ fn check_var_in_constraints<'r>( Ok(vars) } -fn check_var_in_transform<'r>( +fn check_var_in_transform( mut vars: RapidSet, - transform: &'r Option, + transform: &Option, ) -> RResult> { let Some(transform) = transform else { return Ok(vars);