CVE Patch Review

CVE-2026-6321: fast-uri fixes traversal by preserving reserved path escapes

CVE-2026-6321 · Updated 2026-05-08 Root-cause

Summary

The patch addresses a path traversal flaw caused by decoding percent-encoded path bytes before normalization. The fix replaces broad unescape/escape flows with context-aware percent-encoding normalization and path-specific handling that preserves reserved escapes such as %2F and %2E as data. Added regression tests directly cover traversal and path-separator confusion cases. Overall, the change appears to correct the normalization-order bug at its source for the affected path handling logic.

Analysis

Vulnerability

CVE-2026-6321 describes a path traversal issue in fast-uri ≤ 3.1.0 where percent-encoded URI characters were decoded before path normalization. In the vulnerable flow shown in the patch digest, code paths in index.js applied unescape() to whole URIs and path components before subsequent normalization and serialization, and also used escape(unescape(parsed.path)) on parsed paths. That ordering can transform encoded dot segments such as %2e%2e into live .. syntax before dot-segment removal or path comparison, enabling traversal-style bypasses of filters that only inspect normalized output or equality checks.

The patch summary and tests show two concrete exploit classes: encoded dot segments like /public/%2e%2e/admin and encoded separators like /a%2Fb. If reserved escapes are decoded too early, the library can incorrectly treat path data as structural syntax. This is consistent with the issue description in the CVE record and NVD entry.

// Vulnerable pattern from the patch digest (index.js / path handling)
uriA = unescape(uriA)
uriB = unescape(uriB)
component.path = unescape(component.path)
parsed.path = escape(unescape(parsed.path))

Patch

The fix in commit 876ce79b662c3e5015e4e7dffe6f37752ad34f35 removes generic decode-before-normalize behavior and replaces it with context-aware helpers in lib/utils.js:

  • normalizePercentEncoding(input, decodeUnreserved = false) uppercases valid escapes and optionally decodes only unreserved ASCII bytes.
  • normalizePathEncoding(input) normalizes path data while explicitly preserving reserved escapes and refusing to decode . from percent-encoding.
  • escapePreservingEscapes(input) escapes raw characters without destroying already-valid percent escapes.

In index.js, the patch removes broad calls to unescape() and normalizeComponentEncoding(), replacing them with parse/serialize flows that preserve component boundaries and path semantics. Path handling now uses escapePreservingEscapes, normalizePercentEncoding, and normalizePathEncoding instead of converting reserved escapes into live syntax.

// Patched helpers from the digest
function normalizePercentEncoding (input, decodeUnreserved = false) {
  // valid escapes uppercased; only unreserved bytes optionally decoded
}

function normalizePathEncoding (input) {
  // preserves reserved escapes and does not decode '.' from %2E
}

function escapePreservingEscapes (input) {
  // escapes raw bytes while preserving existing %XX sequences
}

The added regression tests in test/security-normalization.test.js are well targeted. They verify that parsing preserves %2F and %2E%2E as path data, normalization keeps encoded traversal payloads encoded, and equality checks no longer collapse encoded separators or dot segments into equivalent live paths. The README update also documents the intended invariant that reserved path escapes such as %2F and %2E are preserved during normalization and comparison.

Review

Pros

  • The patch addresses the core bug class: improper normalization order. It stops decoding reserved path escapes before path normalization and comparison.
  • The new helper split is technically sound. Percent-encoding normalization is now component-aware instead of relying on generic whole-component escape/unescape transforms.
  • normalizePathEncoding() explicitly protects the dangerous edge case by not decoding . from percent-encoding, preventing encoded dot segments from becoming live traversal syntax.
  • escapePreservingEscapes() avoids corrupting already-valid escapes, which reduces double-transform risk during serialization.
  • Regression coverage is directly aligned with the vulnerability: encoded .., encoded /, parse behavior, normalize behavior, and equality semantics.
  • The documentation update clarifies the security contract for reserved path escapes, which should help downstream users reason about behavior.

Cons

  • The review evidence is limited to the provided digest, so it does not fully establish whether all call sites and all URI components received equivalent hardening beyond the shown path-centric changes.
  • The implementation still relies on legacy escape() semantics in helper code. While the patch improves ordering and preservation logic, continued dependence on legacy escaping APIs may be a maintainability concern.
  • The tests focus on path traversal primitives but do not show broader adversarial coverage for malformed percent sequences, mixed-case escapes across all components, or interactions with authority/query/fragment normalization.
  • The patch appears intentionally path-specific, which is correct for this CVE, but consumers should not infer that all encoded delimiter confusion issues in non-path components are covered by these tests.

Verdict

Root-cause.

This patch appears to fix the vulnerability at the correct abstraction layer by eliminating decode-before-normalize behavior and introducing path-aware percent-encoding handling that preserves reserved escapes as data. The added tests demonstrate that encoded dot segments and separators are no longer reinterpreted as live path syntax during parse, normalize, or equality operations. Based on the supplied sources, upgrading to 3.1.1 is the appropriate remediation for CVE-2026-6321, and the commit 876ce79b662c3e5015e4e7dffe6f37752ad34f35 is consistent with a source-level fix rather than a narrow filter bypass patch.

Sources