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);