Sep 2, 2026·6 min read·54 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.
CVE-2026-109619 (PHP2Shell) is a critical Use-After-Free vulnerability in the core PHP engine caused by improper global state cleanup during out-of-memory exception handling across persistent worker requests.
In Python virtualenv prior to version 21.14.9, activation script generators and configuration writers use Path.exists() before writing files. Because Path.exists() resolves symbolic links, it returns False for dangling symlinks. Consequently, virtualenv skips unlinking pre-planted symbolic links or hard links, writing data directly to target locations outside the intended virtual environment directory.
A remote code execution vulnerability exists in Microsoft .NET due to untrusted data deserialization within the PresentationCore component. The flaw stems from implicit auto-conversion flags during clipboard data retrieval operations.
An out-of-bounds write vulnerability in Microsoft .NET Framework, .NET runtime, and Visual Studio allows unauthenticated remote attackers to execute arbitrary code via crafted pixel-shader bytecode or auto-converted clipboard payloads.
ASP.NET Core contains a thread deadlock vulnerability (CWE-833) that allows an unauthenticated remote attacker to cause a denial of service over the network by exhausting server thread pool resources.
CVE-2026-70035 is a high-severity Remote Code Execution (RCE) vulnerability in Microsoft .NET Framework, .NET runtime, and Visual Studio caused by improper handling during the deserialization of untrusted data (CWE-502). An unauthenticated attacker can execute arbitrary code within the context of the affected process by delivering a crafted serialized payload.