Sep 2, 2026·6 min read·2 visits
The fast-uri package redundantly decodes host components twice across the parsing and recomposition lifecycle. Attackers can leverage nested percent-encoded host strings to bypass security boundary checks, leading to unauthenticated SSRF to internal network nodes and cloud metadata endpoints.
A double-decoding vulnerability in the fast-uri package allows unauthenticated remote attackers to bypass host-policy validation and conduct Server-Side Request Forgery (SSRF) attacks by submitting nested percent-encoded URI strings.
The high-performance, dependency-free URI parsing library fast-uri is heavily utilized within the Node.js ecosystem (such as in the Fastify framework) for routing, URL normalization, and address resolution. In modern microservice architectures, application gateways and reverse proxies frequently rely on URI parsers to inspect and validate destination hostnames before forwarding outbound network requests.
This vulnerability is characterized as an incomplete fix for a precursor flaw (CVE-2026-6322) and belongs to the double-decoding vulnerability class (CWE-174). Due to incorrect processing of percent-encoded hostnames, the library decodes the host component twice during its parsing and serialization lifecycle.
This behavior directly breaches RFC 3986, Section 2.4, which explicitly states that a URI processor must not decode the same string multiple times. The resulting parser differential permits attackers to craft obfuscated URIs that pass application-level validation blocklists but serialize into restricted destinations when handed to backend HTTP network clients.
The technical breakdown of the flaw involves two distinct phases inside the fast-uri lifecycle: parsing and recomposition.
In the initial parser phase, handled by the parseWithStatus function in index.js, the library processes the raw host string. It attempts to extract and normalize the hostname, applying the legacy global JavaScript function unescape() to decode percent-encoded sequences. If an input string contains double-encoded characters, such as %256c (where %25 decodes to the percent symbol % and 6c represents the letter l), the parser decodes only the outer sequence. The resulting intermediate host saved in memory is %6c.
When the application executes security validations against the parsed object, it inspects this intermediate string. Because %6c does not match restricted host literal strings like localhost or local IP segments, the validation logic flags the URL as safe. This mismatch constitutes a critical parser differential.
During the subsequent recomposition, normalization, or resolution phase in lib/utils.js, the recomposeAuthority function is invoked to format the URI for outgoing requests. Inside this function, fast-uri historically called unescape() on the stored hostname a second time. This second pass decodes %6c into the literal character l. When the completed URI is serialized and handed to a network client (such as standard Node.js http, undici, or axios), the client routes the request to the fully decoded, restricted host, bypassing the validation layer.
The maintainers patched the vulnerability across versions 2.4.5, 3.1.6, and 4.1.3. The key change replaces raw unescape() calls during parsing and recomposition with a safe helper function named normalizePercentEncoding.
In the parsing logic of index.js, the unescaping logic was modified as follows:
@@ -460,15 +468,15 @@ function parseWithStatus (uri, opts) {
if (parsed.scheme !== undefined) {
parsed.scheme = unescape(parsed.scheme)
}
if (parsed.host !== undefined) {
- parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP)
+ parsed.host = reescapeHostDelimiters(normalizePercentEncoding(parsed.host, true), isIP)
}
}Similarly, within the serialization phase inside lib/utils.js, the secondary unescape() call was replaced:
@@ -539,8 +548,8 @@ function recomposeAuthority (component) {
if (component.host !== undefined) {
- let host = unescape(component.host)
+ // Decode only unreserved bytes, once. In particular, keep %25 encoded so
+ // it cannot become the introducer for a second escape during recomposition.
+ let host = normalizePercentEncoding(component.host, true)
if (!isIPv4(host)) {The newly introduced normalizePercentEncoding helper solves the root cause by strictly limiting decoding to safe, unreserved ASCII characters (alphanumerics, hyphen, period, underscore, tilde) while explicitly preserving the percent sign representation %25 in its encoded form. By keeping %25 intact, nested percent-encoded sequences are prevented from becoming active percent characters during downstream serialization passes, neutralising the recursive decoding exploit path.
Exploitation of CVE-2026-75899 requires no authentication and can be executed via three main payload techniques targeting different internal infrastructure topologies.
The first vector targets loopback hostnames. An attacker targets an internal administrative portal by crafting a nested URL:
http://%256c%256f%2563%2561%256c%2568%256f%2573%2574/private-api
When parsed, the host is resolved to %6c%6f%63%61%6c%68%6f%73%74. If validation filters block string-exact matches for localhost, this check passes. Upon serialization, the application makes a request to localhost/private-api.
The second vector evades IP-based restriction lists by obfuscating separator characters. The IP address loopback 127.0.0.1 contains dot separator characters. Since the dot character evaluates to %2e when percent encoded, double-encoding results in %252e or %252E. The attacker submits the following payload:
//127%252e0%252e0%252e1/admin
The validation logic analyzes 127%2e0%2e0%2e1 as a benign remote string. The recomposition layer decodes this back into 127.0.0.1, initiating an unauthorized local network request.
The third vector targets cloud metadata endpoints to extract authentication credentials in AWS, Azure, or GCP environments. The cloud metadata IP address 169.254.169.254 is obfuscated via nested percent encoding:
//169%252E254%252E169%252E254/latest/meta-data/
The double-decoded request routes directly to the Link-Local Instance Metadata Service (IMDS), allowing the remote attacker to extract temporary cloud role credentials.
The impact of this vulnerability is critical for applications that process untrusted user-supplied URIs and implement internal security boundaries. A successful exploitation allows complete bypass of network restrictions, enabling Server-Side Request Forgery (SSRF).
If the host application executes with elevated network privileges or resides inside an isolated virtual private cloud (VPC), the attacker can access sensitive local systems. This includes internal databases, microservice administration endpoints, key-value stores, and credential configuration dashboards.
The CVSS v3.1 score of 7.5 reflects high impact on integrity. Because fast-uri does not directly execute code itself, the confidentiality and availability scores are technically defined as none within the scope of this isolated library. However, the downstream architectural impact of SSRF can result in complete system compromise or data exfiltration depending on the capabilities of the reached internal endpoints.
The primary remediation step is upgrading the fast-uri dependency to a secure, patched version. The specific secure versions correspond to the version branch maintained by the project.
For projects utilizing the 4.x branch, upgrade to 4.1.3 or later:
npm install fast-uri@4.1.3
For projects utilizing the 3.x branch, upgrade to 3.1.6 or later:
npm install fast-uri@3.1.6
For projects utilizing the 2.x branch, upgrade to 2.4.5 or later:
npm install fast-uri@2.4.5
In scenarios where immediate patching is unfeasible, implement a temporary input validation wrapper that rejects incoming URLs containing percent signs (%) in the host block. This effectively blocks nested percent encodings and mitigates double-decoding attempts before the string is passed to fast-uri parsing functions.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
fast-uri Fastify / OpenJS Foundation | >= 2.4.1 < 2.4.5 | 2.4.5 |
fast-uri Fastify / OpenJS Foundation | >= 3.1.2 < 3.1.6 | 3.1.6 |
fast-uri Fastify / OpenJS Foundation | >= 4.0.0 < 4.1.3 | 4.1.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-174 / CWE-918 |
| Attack Vector | Network (AV:N) |
| CVSS v3.1 Score | 7.5 (High) |
| EPSS Score | 0.00234 |
| Impact Type | Integrity (SSRF / Host Bypass) |
| Exploit Status | poc |
| CISA KEV Status | Not Listed |
The application decodes the same input data multiple times, which can allow malicious input to bypass validation checks that only look at the first level of decoding.
A critical parser differential and host confusion vulnerability (CVE-2026-76172) exists in fast-uri, a dependency-free URI validation and normalization library for Node.js. This vulnerability stems from improper validation of the URI scheme component after decoding percent-encoded characters using the legacy global unescape() function. This allows structural characters such as path delimiters and control characters to be written raw into the output stream during serialization, causing host confusion, Server-Side Request Forgery (SSRF), or HTTP response splitting downstream.
A critical parser differential vulnerability in the Node.js fast-uri library allows unauthenticated remote attackers to bypass address-validation filters and perform Server-Side Request Forgery (SSRF). The library fails to validate complete IPv6 grammar inside bracketed literals, silently truncating invalid trailing characters and normalising malformed hosts into valid loopback or private addresses.
A host confusion vulnerability exists in the fast-uri Node.js library when parsing scheme-relative URI references. Due to inconsistent domain name canonicalization, applications validating resolved hosts can be bypassed by downstream WHATWG-compliant parsers, facilitating Server-Side Request Forgery (SSRF).
Sulu CMS, an open-source PHP content management system based on the Symfony framework, is affected by an Insecure Direct Object Reference (IDOR) vulnerability within its media relocation API. Authenticated users with restricted edit permissions can relocate media out of secure, unauthorized collections into folders they control, bypassing access controls entirely. This security issue is tracked under CVE-2026-82395 and GHSA-h6cx-gjxx-v25c.
An incomplete sanitization fix for CVE-2026-35536 in Tornado allowed cookie attribute injection. The framework's validation loop checked lowercase keyword arguments but neglected legacy case-insensitive parameters passed through arbitrary keyword arguments, which Python's underlying library parses case-insensitively.
GHSA-8423-8FGW-73VQ is a pre-authentication denial of service vulnerability in the Tornado web server's handling of multipart/form-data. The flaw allows an unauthenticated remote attacker to cause memory exhaustion and CPU starvation by transmitting a crafted HTTP request containing a high density of boundary delimiters. Because Tornado splits the entire request body in memory prior to enforcing the max_parts validation threshold, the Python interpreter attempts to materialize a massive list of byte segments. This triggers an immediate memory exhaustion (OOM) crash or server-wide CPU starvation before the payload can be validated and rejected.