Skip to content

Tavano/fix path traversal#1525

Merged
guitavano merged 5 commits intomainfrom
tavano/fix-path-traversal
Feb 10, 2026
Merged

Tavano/fix path traversal#1525
guitavano merged 5 commits intomainfrom
tavano/fix-path-traversal

Conversation

@guitavano
Copy link
Contributor

@guitavano guitavano commented Feb 10, 2026

What is this Contribution About?

Please provide a brief description of the changes or enhancements you are proposing in this pull request.

Issue Link

Please link to the relevant issue that this pull request addresses:

Loom Video

Record a quick screencast describing your changes to help the team understand and review your contribution. This will greatly assist in the review process.

Demonstration Link

Provide a link to a branch or environment where this pull request can be tested and seen in action.


Summary by cubic

Hardened the HTTP client against path traversal with strict per-parameter normalization, validation, and URL-encoding. Invalid params now return a generic 400 error; also standardized debug redaction and fixed a typo.

  • Bug Fixes
    • Added normalizePathParam: repeatedly decodes; blocks .., absolute paths, . segments, and null bytes; normalizes slashes.
    • Applied per-parameter validation to single and wildcard array params; URL-encodes each value; maps validation failures to HttpError(400) without exposing input.
    • Standardized Authorization redaction to and fixed “Unknown path” typo.

Written for commit 4c2fd24. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Richer HTTP error reporting for clearer failure details.
    • Strongly typed request/response shapes and broader URL pattern support for more flexible routes.
    • Configurable client options: header processing, custom fetch implementation, and control over empty path segments.
    • Automatic sanitization and encoding of path parameters for safer, correct URLs.
  • Bug Fixes

    • Fixed typos and improved debug/redaction output for clearer logs.

@github-actions
Copy link
Contributor

Tagging Options

Should a new tag be published when this PR is merged?

  • 👍 for Patch 0.133.32 update
  • 🎉 for Minor 0.134.0 update
  • 🚀 for Major 1.0.0 update

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 10, 2026

📝 Walkthrough

Walkthrough

Adds typed HTTP primitives and an HttpError class, extends HttpClientOptions, and implements path-parameter normalization/validation integrated into URL construction and debug logging for the HTTP utility.

Changes

Cohort / File(s) Summary
Core HTTP utility
utils/http.ts
Added HttpError, TypedRequestInit<T>, TypedResponse<T>; expanded URLPatternParams and ClientOf types; added private normalizePathParam and integrated path param normalization/encoding into URL construction and debug logging; minor typo and redaction label fixes.
HttpClient configuration
utils/http.ts
Extended HttpClientOptions with `processHeaders?: (headers?: Headers)=>Headers

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant UrlBuilder as URL Builder
  participant Normalizer as normalizePathParam
  participant Fetcher as fetcher
  participant Processor as processHeaders
  participant Server as External API

  Client->>UrlBuilder: build URL from pattern + params
  UrlBuilder->>Normalizer: normalize each path param (value, name)
  Normalizer-->>UrlBuilder: sanitized/encoded segment or throw HttpError
  UrlBuilder-->>Client: final sanitized URL
  Client->>Fetcher: send request to final URL (using configured fetcher)
  Fetcher->>Server: HTTP request
  Server-->>Fetcher: HTTP response
  Fetcher->>Processor: provide response.headers (if configured)
  Processor-->>Client: processed headers
  Fetcher-->>Client: response body or throw HttpError
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰
I hopped through segments, scrubbed each one clean,
Encoded and safe, no "../" seen,
Typed requests snug in neat little rows,
Headers trimmed, fetcher ready — off it goes,
Hooray for safe routes and tidy debug scenes! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Tavano/fix path traversal' is directly related to the main objective: hardening the HTTP client against path traversal attacks through parameter normalization and validation.
Description check ✅ Passed The description includes the template structure with sections for contribution details, issue link, Loom video, and demonstration link, plus an auto-generated summary explaining the security fix and changes made.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch tavano/fix-path-traversal

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
utils/http.ts (2)

103-161: Solid path-traversal mitigation — one minor defense-in-depth suggestion.

The iterative decode → validate → normalize → re-validate pipeline is well thought out. One small ordering improvement: move the null-byte check (Step 6) to right after decoding (Step 1), before any string manipulation. While JavaScript strings aren't null-terminated, catching poisoned input earlier is cleaner and avoids any risk of future refactors accidentally processing the value before the check.

🛡️ Suggested reorder
   } catch {
     // If decode fails, keep the last successful decoded value
     // Do not reset to original string as that would bypass security checks
   }
 
+  // Early check: Block null bytes
+  if (decoded.includes("\0")) {
+    throw new Error(
+      `Null byte detected in parameter '${paramName}'`,
+    );
+  }
+
   // Step 2: Check for path traversal in decoded value
   if (decoded.includes("..")) {

And remove the duplicate check at the end (lines 153–158).


290-295: The typeof x === "number" branch in the filter is now unreachable.

Since all defined params are run through String() + encodeURIComponent() (returning strings), and non-template segments are already strings, the number check on line 293 is dead code. Not a bug, but could be cleaned up for clarity.

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@cubic-dev-ai cubic-dev-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.

1 issue found across 1 file

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="utils/http.ts">

<violation number="1" location="utils/http.ts:124">
P1: Bug: `decoded.includes("..")` is overly broad — it rejects any parameter value containing two consecutive dots (e.g., `file..name`, `v1..v2`) even when they aren't path traversal. Step 5 already correctly validates individual path segments with `segment === ".."`. This substring check should be removed or narrowed to a segment-based regex to avoid false positives on legitimate inputs.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

}

// Step 2: Check for path traversal in decoded value
if (decoded.includes("..")) {
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 10, 2026

Choose a reason for hiding this comment

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

P1: Bug: decoded.includes("..") is overly broad — it rejects any parameter value containing two consecutive dots (e.g., file..name, v1..v2) even when they aren't path traversal. Step 5 already correctly validates individual path segments with segment === "..". This substring check should be removed or narrowed to a segment-based regex to avoid false positives on legitimate inputs.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At utils/http.ts, line 124:

<comment>Bug: `decoded.includes("..")` is overly broad — it rejects any parameter value containing two consecutive dots (e.g., `file..name`, `v1..v2`) even when they aren't path traversal. Step 5 already correctly validates individual path segments with `segment === ".."`. This substring check should be removed or narrowed to a segment-based regex to avoid false positives on legitimate inputs.</comment>

<file context>
@@ -89,6 +97,69 @@ export interface HttpClientOptions {
+  }
+
+  // Step 2: Check for path traversal in decoded value
+  if (decoded.includes("..")) {
+    throw new Error(
+      `Path traversal detected in parameter '${paramName}': ${str}`,
</file context>
Suggested change
if (decoded.includes("..")) {
if (decoded.split(/[\/\\]/).some(seg => seg === "..")) {
Fix with Cubic

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@utils/http.ts`:
- Around line 124-128: The thrown errors currently include raw user input (the
variable `str`) which can be reflected into HTTP responses; update the
validation to avoid echoing `str` in error messages (use a generic message
referencing `paramName` and the issue, e.g., "Invalid parameter" or "Path
traversal detected") and ensure callers of this validation (where `decoded`,
`paramName`, and `str` are used) catch these thrown errors and translate them
into a generic 400 response body without including the original input.
- Around line 259-279: The change applies encodeURIComponent to normalized path
params (using normalizePathParam) which encodes slashes so single-string
wildcard params like "path/to/file" become "path%2Fto%2Ffile" and no longer
create multiple path segments; update the public documentation and inline
comment near the wildcard handling code (the block that handles
Array.isArray(param) and the single-value path branch) to explain this
behavioral change and instruct callers to pass wildcard segments as arrays
(e.g., ["path","to","file"]) if they relied on literal slashes, and add/adjust
one unit test for wildcard routing to assert the new behavior; mention
encodeURIComponent, normalizePathParam, and the wildcard route pattern (/*) in
the docs/comment.
🧹 Nitpick comments (2)
utils/http.ts (2)

100-161: normalizePathParam is well-designed; two observations worth noting.

  1. Over-broad .. check (line 124): decoded.includes("..") rejects any parameter containing two consecutive dots anywhere, including legitimate filenames like my..file.txt. This is the safe/conservative choice for a security function, but document this trade-off so callers know.

  2. Null byte check ordering (Step 6): Consider moving the null-byte check (lines 153-158) before the normalization regex in Step 4. A null byte in decoded could potentially affect regex behavior in some engines. Practically this is fine in V8/Deno, but checking and rejecting null bytes as early as possible is a defense-in-depth best practice.

Suggested reorder: check null bytes right after Step 2
   // Step 2: Check for path traversal in decoded value
   if (decoded.includes("..")) {
     throw new Error(
       `Path traversal detected in parameter '${paramName}': ${str}`,
     );
   }
 
+  // Step 3: Block null bytes (moved up for defense-in-depth)
+  if (decoded.includes("\0")) {
+    throw new Error(
+      `Null byte detected in parameter '${paramName}': ${str}`,
+    );
+  }
+
   // Step 3: Block absolute paths
   if (decoded.startsWith("/") || decoded.startsWith("\\")) {

(and remove the original Step 6 block)


110-121: Iterative decoding loop is solid, but consider a max-iteration guard.

The while (prev !== decoded) loop terminates when decoding produces no change. In theory this always converges, but a maliciously crafted string that somehow causes decodeURIComponent to oscillate (extremely unlikely) would loop forever. A simple iteration cap (e.g., 10) provides defense-in-depth at zero cost.

Suggested safeguard
   let decoded = str;
   try {
     let prev = "";
-    while (prev !== decoded) {
+    let iterations = 0;
+    while (prev !== decoded && iterations++ < 10) {
       prev = decoded;
       decoded = decodeURIComponent(decoded);
     }
   } catch {

@guitavano guitavano force-pushed the tavano/fix-path-traversal branch from b1f503e to f9b4d4a Compare February 10, 2026 17:09
@guitavano guitavano merged commit 3ff0d90 into main Feb 10, 2026
3 checks passed
@guitavano guitavano deleted the tavano/fix-path-traversal branch February 10, 2026 17:32
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