CVE-2026-6321: fast-uri fixes traversal by preserving reserved path escapes
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/unescapetransforms. 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.
Recommended Labs
Try this vulnerability pattern yourself with hands-on labs.
- Path Traversal.js
Best direct match for CVE-2026-6321 because it is a JavaScript hands-on lab focused on classic path traversal (CWE-22). It is a good starting point for practicing defensive fixes around canonicalization, normalization order, and blocking encoded ../ style traversal payloads.
- Path Traversal II.js
Strong follow-up lab for this CVE topic because the vulnerability involves improper normalization order and percent-decoding bypasses. This medium JavaScript lab is more likely to reinforce defense-in-depth patterns such as safe path resolution, validation after decoding, and context-aware normalization.
- Slipstream.js
Useful adjacent practice for encoded path abuse in JavaScript. While not named exactly as a traversal lab, it maps to the same weakness family (CWE-22/23/36/73) and helps build secure handling habits for file-path processing and traversal-style input validation in Node.js applications.