Sep 30, 2026·6 min read·5 visits
A validation discrepancy in fast-uri allows remote attackers to smuggle mailto parameters such as 'to', 'subject', and 'body' by using percent-encoded names like '%74o', bypassing safety validations before serialization.
A security-critical desynchronization vulnerability exists in fast-uri versions 4.1.3 and 4.1.4. Due to incorrect order-of-operations, the mailto scheme parser validates raw percent-encoded parameter keys instead of normalized keys, but subsequently decodes and writes them into a generic headers object. When the parsed URI is serialized, these keys are re-emitted literally, allowing attackers to bypass validation boundaries and smuggle unauthorized recipients, subjects, or body parameters in downstream mailing applications.
The npm package fast-uri is a dependency-free RFC 3986 URI parsing and serialization toolbox for Node.js. It serves as a performance-critical dependency for several high-volume ecosystem packages, including the Fastify web framework and the ajv JSON schema validator. In version 4.1.3, the package introduced a dedicated parser to handle the mailto URI scheme.
During its parsing operations, fast-uri separates standard, reserved mailto query parameters—specifically to, subject, and body—from user-defined, generic headers. This separation is critical for downstream applications that rely on the parsed output to validate email destinations and enforce local delivery policies before executing mail actions.
The vulnerability is classified under a combination of CWE-172 (Encoding Error) and CWE-436 (Interpretation Conflict). Because the library compares incoming query keys to reserved keys while they are still percent-encoded, it fails to recognize encoded reserved words. The subsequent decoding of these keys prior to internal storage creates an interpretation conflict between the validation phase and the serialization phase.
The root cause of this flaw lies in the sequencing of percent-decoding relative to token validation within the mailtoParse function. When processing a query parameter string such as ?%74o=attacker@evil.test, the parser extracts the key as %74o and compares it directly against string literals.
Because %74o does not literally match the string "to", the parser classifies the parameter as a generic, non-reserved header. However, during the storage phase, the parser executes decodeHex() on the key, which translates %74o to "to". This decoded key is then stored in a generic headers map as headers["to"] = "attacker@evil.test".
This discrepancy creates a desynchronization between the structured fields of the parsed object. An application auditing parsed.to will only observe the legitimate, safe recipients, while the malicious recipient remains hidden inside parsed.headers.to. When the object is passed to mailtoSerialize, the serializer loops over the keys of the headers map, encodes them, and appends them to the query string, re-emitting the literal to=attacker@evil.test parameter.
A review of the vulnerable implementation in lib/schemes.js reveals the processing loop that causes the discrepancy:
// Vulnerable parsing logic in fast-uri v4.1.4
const name = eqIdx === -1 ? token : token.slice(0, eqIdx);
const value = eqIdx === -1 ? '' : token.slice(eqIdx + 1);
if (name === 'to') {
const addrs = value.split(',');
for (let j = 0; j < addrs.length; j++) to.push(addrs[j]);
continue;
}
if (name === 'subject') {
mailtoComponent.subject = decodeHex(value);
continue;
}
if (name === 'body') {
mailtoComponent.body = decodeHex(value);
continue;
}
if (headers === null) headers = (Object.create(null));
headers[decodeHex(name)] = decodeHex(value);In the block above, the variable name is compared to 'to', 'subject', and 'body' without prior decoding. If the attacker passes %74o, these comparisons evaluate to false. The execution path falls through to the final lines, where decodeHex(name) translates the key to 'to' and writes it into the headers dictionary.
The fix introduced in 4.1.5 corrects this by decoding the parameter name before performing any logic checks, while also adding lower-case normalization to prevent case-based bypasses (e.g., TO or To escaping detection):
// Corrected parsing logic in fast-uri v4.1.5
const name = decodeHex(eqIdx === -1 ? token : token.slice(0, eqIdx));
const normalizedName = name.toLowerCase();
const value = eqIdx === -1 ? '' : token.slice(eqIdx + 1);
if (normalizedName === 'to') {
const addrs = value.split(',');
for (let j = 0; j < addrs.length; j++) to.push(addrs[j]);
continue;
}Exploitation relies on a common software pattern: an application receives a URI, parses it to extract and validate properties, serializes the validated object back into a string, and then executes or transmits that string to a downstream system.
Consider an application that validates target email addresses against an internal allowlist. If the input is mailto:admin@company.com?%74o=attacker@evil.test, the validation system queries the parsed representation. The library returns parsed.to = ['admin@company.com'], which successfully matches the allowlist of @company.com.
// Conceptual application bypass flow
const parsed = fastURI.parse("mailto:admin@company.com?%74o=attacker@evil.test");
console.log(parsed.to); // ['admin@company.com'] -> Safe domain check passes
// Serialization generates the final payload
const finalURI = fastURI.serialize(parsed);
console.log(finalURI); // "mailto:admin@company.com?to=attacker@evil.test"Because the verification logic only inspected the primary to array, the injected address hidden in headers.to bypassed the filter. Upon serialization, the library constructs a string containing two valid destination arguments, instructing the executing client to route the message to the unauthorized recipient.
The overall impact of CVE-2026-86818 depends heavily on the context of the downstream processing application. When used within automated registration, notification, or support-ticket workflows, this desynchronization allows attackers to force the sending of notifications to arbitrary accounts.
Attackers can leverage this bypass to intercept confidential emails by adding unauthorized CC or BCC entries. If the system constructs sensitive notifications containing password resets or system data, an attacker can append their email as a primary recipient through the serialized parameter injection.
The CVSS v3.1 vector is rated as 4.8 (Medium severity). Because exploitation requires a specific configuration where an application parses, validates, and subsequently re-serializes the mailto link prior to execution, the attack complexity is classified as High (AC:H). This vulnerability does not present opportunities for direct remote code execution or privilege escalation within the Node.js runtime itself.
To fully resolve CVE-2026-86818, developers must upgrade fast-uri to version 4.1.5 or later. This release addresses both the parsing order-of-operations and serialization canonicalization to ensure identical representations during both phases.
Because fast-uri is heavily utilized as a transitive dependency, standard direct updates may not immediately remediate nested packages. To enforce the updated library across the dependency tree, developers can utilize package manager overrides:
{
"overrides": {
"fast-uri": "^4.1.5"
}
}For environments where immediate updates are blocked, a manual mitigation policy must be implemented. Applications should sanitise user-supplied URIs by decoding and verifying all elements inside the nested headers object, or by stripping any occurrences of to, subject, or body from the headers map prior to serialization.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
fast-uri Fastify / OpenJS Foundation | >= 4.1.3, <= 4.1.4 | 4.1.5 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-172 / CWE-436 |
| Attack Vector | Network (AV:N) |
| CVSS v3.1 Score | 4.8 |
| Exploitability Subscore | 2.2 |
| Impact Subscore | 2.5 |
| Exploit Status | poc |
| KEV Status | Not Listed |
The software processes data encoded in multiple formats and does not properly normalize or decode the payload before performing critical parsing operations, causing an interpretation mismatch between security validation and serialization.
CVE-2026-101894 is a critical path traversal vulnerability in @xhmikosr/decompress before versions 10.2.2 and 11.1.4, stemming from an incomplete hardening bypass of CVE-2026-53486 where static lexical containment checks fail to detect kernel-level resolution of crafted symlink chains, allowing arbitrary local file modification and execution.
CVE-2026-86472 is a validation bypass vulnerability in fast-uri (a high-performance RFC 3986 URI toolbox heavily used by popular Node.js frameworks like Fastify and validation libraries like AJV). The vulnerability stems from improper handling of case sensitivity (CWE-178) due to an incorrect order of operations during hostname canonicalization in scheme-relative URLs. An attacker can leverage percent-encoded uppercase characters within scheme-relative URLs to bypass domain blocklists/allowlists in downstream applications. Because hostname resolution in DNS and HTTP is case-insensitive, the bypassed host representation still routes to the target destination, resulting in potential Server-Side Request Forgery (SSRF) or security control bypasses.
An unauthenticated remote attacker can crash NestJS microservices utilizing TCP or RabbitMQ transport layers. The vulnerability exists due to recursive serialization of deeply nested message patterns using JSON.stringify, leading to a RangeError and process termination.
A resource management vulnerability in the Undici HTTP client (CWE-772) occurs when the retry interceptor receives a partial body payload followed by a non-retryable response error on a subsequent connection attempt, resulting in orphaned streams and potential Denial of Service (DoS).
A vulnerability in PyJWT's JWK Set parsing logic allows a malformed RSA key to trigger an unhandled ValueError, leading to an application-wide or request-level Denial of Service.
An uncontrolled recursion vulnerability exists in Nodemailer versions up to and including 10.0.1. When parsing recipient email addresses, recursively nested arrays bypass the parser's depth limit, resulting in V8 call stack exhaustion and immediate synchronous process termination.