🛡️ Sentinel: [CRITICAL] Fix path traversal in typescript extractor#262
🛡️ Sentinel: [CRITICAL] Fix path traversal in typescript extractor#262bashandbone wants to merge 1 commit into
Conversation
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, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
Reviewer's guide (collapsed on small PRs)Reviewer's GuideFixes critical path traversal in the TypeScript dependency extractor by hardening manual path normalization around Flow diagram for secure ParentDir handling in TypeScriptDependencyExtractor path normalizationflowchart TD
A[Component is ParentDir] --> B{components.last exists?}
B -- no --> C[Push ParentDir into components]
B -- yes --> D{last is RootDir or Prefix}
D -- yes --> E[Do nothing
// keep RootDir or Prefix]
D -- no --> F{last is ParentDir}
F -- yes --> G[Push ParentDir into components
// preserve relative traversal]
F -- no --> H[Pop last component
// normal directory backtrack]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- The
ParentDirhandling branch is getting a bit dense; consider extracting this normalization logic into a small helper function (e.g.,normalize_component(components, component)) so the main loop stays easier to read and reason about. - It might be worth adding a brief comment or assertion describing the intended invariant for
components(e.g., never containingRootDirafter aParentDir), so future refactors to this path logic don’t accidentally reintroduce traversal issues.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `ParentDir` handling branch is getting a bit dense; consider extracting this normalization logic into a small helper function (e.g., `normalize_component(components, component)`) so the main loop stays easier to read and reason about.
- It might be worth adding a brief comment or assertion describing the intended invariant for `components` (e.g., never containing `RootDir` after a `ParentDir`), so future refactors to this path logic don’t accidentally reintroduce traversal issues.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Pull request overview
This PR addresses a critical path-normalization flaw in the TypeScript dependency extractor’s manual .. handling, preventing invalid “pop past root/prefix” behavior that could otherwise produce malformed paths and enable traversal-like outcomes when canonicalize() can’t be used. It also documents the vulnerability and guidance in the Sentinel journal.
Changes:
- Harden manual path normalization for
Component::ParentDirto avoid poppingRootDir/Prefixand to preserve leading..for relative paths. - Add a Sentinel journal entry describing the vulnerability, impact, and prevention guidance.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| crates/flow/src/incremental/extractors/typescript.rs | Replaces naive .. handling with root/prefix-safe normalization logic. |
| .jules/sentinel.md | Adds a Sentinel journal entry documenting the issue and mitigation guidance. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| 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 { |
| // If canonicalize fails (file doesn't exist), manually resolve | ||
| let mut components = Vec::new(); | ||
| 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); | ||
| } |
| @@ -0,0 +1,5 @@ | |||
|
|
|||
| ## 2024-05-25 - Path Traversal in TypeScript Extractor | |||
🚨 Severity: CRITICAL
💡 Vulnerability: Path traversal and invalid bounds escaping in the TypeScript dependency extractor. When manually resolving paths (e.g., if
canonicalizefails or is skipped), the code naively popped the last path component when encountering..(std::path::Component::ParentDir). This permitted..segments to popRootDir(/) orPrefix, potentially making an absolute path relative or traversing outside intended boundaries. It also caused relative path traversals like../../ato be incorrectly resolved by dropping the..segments when the component list was empty.🎯 Impact: If malicious or deeply nested
../imports were processed, it could lead to the extractor escaping the project root, potentially reading or analyzing unauthorized files on the file system, or causing the analyzer to panic/crash due to malformed path state.🔧 Fix: Replaced the naive
.pop()call with secure path normalization logic. It now checkscomponents.last():Component::RootDirorComponent::Prefix.Component::ParentDirif the component list is empty or the last element is alsoComponent::ParentDir(to correctly preserve relative paths).✅ Verification:
cargo test -p thread-flow --test extractor_typescript_teststhat existing functionality is preserved...after root (/../../etc/passwdresolves to/etc/passwd) and preserves relative paths (../../a).PR created automatically by Jules for task 1978998290590100611 started by @bashandbone
Summary by Sourcery
Fix insecure manual path normalization in the TypeScript dependency extractor to prevent path traversal and document the vulnerability in Sentinel.
Bug Fixes:
Documentation: