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 @@
## 2026-05-09 - Prevent Path Traversal in Manual Path Resolution
**Vulnerability:** The TypeScript dependency extractor (`crates/flow/src/incremental/extractors/typescript.rs`) manually normalized paths without preventing `ParentDir` components from popping `RootDir` or `Prefix`, allowing paths to escape the intended directory.
**Learning:** Manual path normalization (e.g., when `canonicalize()` fails) must strictly handle standard library boundaries like `RootDir` and `Prefix` to prevent path traversal outside of absolute or project boundaries.
**Prevention:** Always check if the path component being popped represents a boundary, or use robust external path normalization libraries instead of rolling custom loops with `std::path::Component`.
Comment on lines +1 to +4
15 changes: 14 additions & 1 deletion crates/flow/src/incremental/extractors/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -808,7 +808,20 @@ impl TypeScriptDependencyExtractor {
for component in resolved.components() {
match component {
std::path::Component::ParentDir => {
components.pop();
let is_empty = components.is_empty();
let is_parent = matches!(
components.last(),
Some(std::path::Component::ParentDir)
);
let is_root = matches!(
components.last(),
Some(std::path::Component::RootDir | std::path::Component::Prefix(_))
);
if is_empty || is_parent {
components.push(component);
} else if !is_root {
components.pop();
}
}
std::path::Component::CurDir => {}
_ => components.push(component),
Expand Down
Loading