Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -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.
21 changes: 19 additions & 2 deletions crates/flow/src/incremental/extractors/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::path::Component> = Vec::new();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Specify the lifetime parameter for std::path::Component to avoid a compilation error.

std::path::Component is declared as pub enum Component<'a>, so using Vec<std::path::Component> without a lifetime won’t compile. Either specify the lifetime (e.g. Vec<std::path::Component<'_>>) or drop the explicit type and rely on inference:

let mut components: Vec<std::path::Component<'_>> = Vec::new();
// or
let mut components = 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),
Expand Down