Skip to content

🛡️ Sentinel: [CRITICAL/HIGH] Fix path traversal in file resolution#187

Open
bashandbone wants to merge 1 commit intomainfrom
sentinel/fix-path-traversal-16129905665493744072
Open

🛡️ Sentinel: [CRITICAL/HIGH] Fix path traversal in file resolution#187
bashandbone wants to merge 1 commit intomainfrom
sentinel/fix-path-traversal-16129905665493744072

Conversation

@bashandbone
Copy link
Copy Markdown
Contributor

@bashandbone bashandbone commented May 1, 2026

🚨 Severity: CRITICAL
💡 Vulnerability: When manually resolving file paths with std::path::Component, Component::ParentDir was unconditionally popping the last component of the vector. If a malicious or malformed string evaluates with parent navigations that hit Prefix (C:\) or RootDir (/), it incorrectly popped those security boundaries off the stack, enabling potential path traversal.
🎯 Impact: Allowed maliciously crafted input paths to escape bounds, converting safe absolute paths into relative arbitrary accesses.
🔧 Fix: Changed path component traversal logic to match the end of the components vector; explicitly ignoring pops if the top of stack is a root or prefix, and safely accumulating parent traversal for relative structures.
Verification: Validated that test cases passing malformed absolute traversal inputs are resolved safely without directory escape. Ran both cargo fmt and cargo clippy and ran the relevant TypeScript extraction tests and library tests locally.


PR created automatically by Jules for task 16129905665493744072 started by @bashandbone

Summary by Sourcery

Fix unsafe path normalization in TypeScript dependency extraction to prevent path traversal when resolving non-canonicalized file paths.

Bug Fixes:

  • Prevent parent directory components from popping root or prefix segments during manual path resolution, avoiding traversal outside intended directories.

Enhancements:

  • Apply minor Rust code cleanups and type signature simplifications in AST and rule engine modules for improved readability.

Documentation:

  • Add Sentinel security note documenting the path traversal vulnerability, its root cause, and recommended prevention patterns for path normalization logic.

- Updated path component popping logic during `..` (ParentDir) handling in `typescript.rs` to ensure relative paths aren't dangerously truncated to arbitrary system paths.
- Added Sentinel journal entry about path normalization vulnerability and pattern prevention.
- Ran formatting and clippy rules to bring files into compliance.

Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 1, 2026 17:37
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 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 @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented May 1, 2026

Reviewer's Guide

Fixes a critical path traversal vulnerability in manual path normalization for TypeScript dependency resolution by preventing ParentDir components from popping root/prefix boundaries, plus minor refactors/formatting and a new Sentinel doc entry.

Sequence diagram for TypeScript dependency path resolution on canonicalization failure

sequenceDiagram
    participant Extractor as TypeScriptDependencyExtractor
    participant FS as FileSystem
    participant Resolver as ManualPathNormalizer

    Extractor->>FS: canonicalize(resolved_path)
    FS-->>Extractor: Error (path does not exist)

    Extractor->>Resolver: normalize(resolved_path.components())
    loop for each component in components
        Resolver->>Resolver: match component
        alt Component::ParentDir
            Resolver->>Resolver: inspect components.last()
            alt last is Prefix or RootDir
                Resolver->>Resolver: ignore ParentDir
            else last is ParentDir or None
                Resolver->>Resolver: push ParentDir
            else last is normal component
                Resolver->>Resolver: pop last component
            end
        else Component::CurDir
            Resolver->>Resolver: skip component
        else other component
            Resolver->>Resolver: push component
        end
    end

    Resolver-->>Extractor: normalized_path
    Extractor->>Extractor: resolved = normalized_path
Loading

File-Level Changes

Change Details Files
Harden manual path normalization to prevent path traversal when resolving TypeScript dependencies.
  • Track path components as std::path::Component values instead of untyped elements
  • On encountering ParentDir, inspect the last component and only pop non-root, non-prefix entries
  • Accumulate ParentDir components when at the beginning of a relative path or when the last element is also ParentDir, and ignore them when the last element is a root or prefix
  • Continue to ignore CurDir components and push all other components through unchanged
crates/flow/src/incremental/extractors/typescript.rs
Apply minor formatting and lifetime/borrow cleanups in AST and rule engine code.
  • Reformat UTF-8 conversion error handling into a single chained expression without behavioral change
  • Expand an assertion on parse tree equality to multi-line formatting
  • Remove unnecessary explicit lifetimes from helper functions and simplify reference types
  • Condense Registration::read method’s error handling chain into a single line without changing logic
crates/ast-engine/src/tree_sitter/mod.rs
crates/rule-engine/src/check_var.rs
crates/rule-engine/src/rule/mod.rs
crates/rule-engine/src/rule/referent_rule.rs
Add a Sentinel security note documenting the path traversal vulnerability and mitigation pattern.
  • Document the original path traversal issue in manual path normalization
  • Capture the correct handling pattern for ParentDir with Prefix/RootDir components
  • Provide guidance for preventing similar bugs in future path component loops
.jules/sentinel.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • The manual Component::ParentDir handling in the TypeScript path resolution is subtle; consider extracting this logic into a small helper (e.g., normalize_components(...)) so the prefix/root/parent behavior is clearly documented in one place and can be reused consistently if other code paths need similar normalization.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The manual `Component::ParentDir` handling in the TypeScript path resolution is subtle; consider extracting this logic into a small helper (e.g., `normalize_components(...)`) so the prefix/root/parent behavior is clearly documented in one place and can be reused consistently if other code paths need similar normalization.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates the incremental TypeScript dependency resolver to harden manual path normalization against path traversal edge cases (specifically preventing .. from popping RootDir/Windows Prefix), with a small set of formatting and signature cleanups across the rule engine and AST engine.

Changes:

  • Hardened manual Component::ParentDir handling in TypeScript module path resolution to avoid popping RootDir/Prefix.
  • Simplified/cleaned up some Rust formatting and removed unused lifetime parameters.
  • Added a Sentinel write-up documenting the traversal issue and the prevention pattern.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
crates/flow/src/incremental/extractors/typescript.rs Fixes manual path normalization so .. can’t remove root/prefix boundaries.
crates/rule-engine/src/rule/referent_rule.rs Formatting simplification in Registration::read().
crates/rule-engine/src/rule/mod.rs Formatting-only change in Rule::defined_vars().
crates/rule-engine/src/check_var.rs Removes unused lifetimes from helper function signatures.
crates/ast-engine/src/tree_sitter/mod.rs Formatting-only changes (string conversion and test assert formatting).
.jules/sentinel.md Adds documentation describing the vulnerability and prevention guidance.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .jules/sentinel.md
Comment on lines +1 to +4
## 2025-02-18 - Prevent Path Traversal by blocking parent popping of prefix/root in path component manipulation
**Vulnerability:** In `crates/flow/src/incremental/extractors/typescript.rs` and potentially elsewhere, path normalization popped standard components blindly when encountering `Component::ParentDir`. This could allow relative paths to evaluate to dangerous arbitrary paths.
**Learning:** Path normalization routines built on top of `std::path::Component` iterations must verify the previous component before popping it for a parent directory escape. `Component::ParentDir` should push to the component stack if the stack is empty, only has another `ParentDir`, or if the last element is `Prefix` or `RootDir` it should be ignored.
**Prevention:** In loops managing components, match on `components.last()` during a `ParentDir` to ensure we do not `pop` root directories or prefixes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants