CVE-2026-76172: fast-uri Host Confusion via Percent-Encoded Scheme Normalization
fast-uri decodes percent-encoded characters in the URI scheme using the legacy unescape() function and writes the decoded result back verbatim, letting an attacker craft a scheme that becomes…

The problem
fast-uri calls the legacy global unescape() on the scheme component and serializes the decoded result back as raw characters, with no re-validation against the RFC 3986 scheme grammar and no re-escaping.
A URI like %2f%2fevil.example:/pwn parses with no authority (host is undefined), but after resolve() or normalize() the output is //evil.example:/pwn, which reparses with host evil.example. The %uXXXX form supported by unescape() triggers the same transformation, and a scheme containing %0d%0a reaches the serialized output as a raw CR LF sequence, enabling header injection.
Proof of concept
A working proof-of-concept for CVE-2026-76172 in fast-uri, with the exact payload below.
const uri = require('fast-uri')
// Before patch: host is undefined on parse, but resolve/normalize surfaces evil.example
console.log(uri.parse('%2f%2fevil.example:/pwn').host)
// => undefined (looks safe)
console.log(uri.normalize('%2f%2fevil.example:/pwn'))
// => //evil.example:/pwn (host is now evil.example)
// Legacy %uXXXX form (also decoded by unescape()) produces the same result
console.log(uri.normalize('%u002f%u002fevil.example:/pwn'))
// => //evil.example:/pwn
// CRLF injection via scheme
console.log(uri.normalize('%0d%0aX-Injected: yes\r\nexample:/path'))
// => raw CR LF lands in serialized outputThe root cause is a single call: scheme = unescape(parsed.scheme) in index.js. The legacy unescape() decodes every percent-sequence, including %2F (slash), %0D%0A (CR LF), and the non-standard %uXXXX Unicode form. The decoded value is written straight back into the serialized URI without re-escaping or checking that the result still conforms to the RFC 3986 scheme grammar (which permits only ALPHA *( ALPHA / DIGIT / '+' / '-' / '.' )).
This means an input that looks like an opaque scheme on parse becomes an authority introducer after normalization, flipping host from undefined to an attacker-controlled value. Any middleware that checks the parsed host before calling normalize() or resolve() sees a safe value, while the downstream consumer sees an entirely different host.
The fix
Upgrade to fast-uri >= 2.4.5 (v2.x line), >= 3.1.6 (v3.x line), or >= 4.1.3 (v4.x line). The patch validates scheme characters after decoding and rejects or re-escapes any scheme that contains characters outside the RFC 3986 allowed set, preventing decoded slashes or control characters from reaching the serialized output.
Reported by OpenJS Foundation CNA.
Related research
- high · 7.5CVE-2026-75899CVE-2026-75899: fast-uri SSRF via Double Hostname Percent-Decoding
- high · 7.5CVE-2026-75975CVE-2026-75975: fast-uri SSRF via Malformed IPv6 Normalization
- high · 7.5CVE-2026-75931CVE-2026-75931: fast-uri Host Confusion via Skipped IDN Canonicalization on Scheme-Relative References
- high · 7.5CVE-2026-18446CVE-2026-18446: fast-uri Host Confusion via Backslash Authority Introducer