It's like a bouncer who checks the guest list before putting on his reading glasses
CVE-2026-86472 affects the fast-uri npm package (versions < 2.4.7, 3.0.0–3.1.7, 4.0.0–4.1.4), a dependency-free RFC 3986 URI toolbox with ~100 million weekly downloads. The bug is a sequencing error: normalize() folds the hostname to lowercase *before* percent-decoding, so %41 (which should decode to a) survives as uppercase A. The result is that parse('//%41.com').host returns A.com while parse('//a.com').host returns a.com. Applications performing case-sensitive host checks against parsed output can reach inconsistent conclusions about the same effective destination.
The vendor's MEDIUM / 4.8 is already modest, but it still overstates the practical risk. The CVSS vector correctly sets AC:H (high attack complexity), yet the real friction is deeper than complexity alone. Over 99% of fast-uri's install base consumes it transitively through AJV and Fastify for JSON Schema $ref resolution — not for security-relevant URL validation. An attacker must find an application that (a) directly uses fast-uri's host output for access-control decisions, (b) performs that comparison case-sensitively, and (c) accepts attacker-controlled URLs. That three-gate chain makes exploitation a niche scenario, not a fleet-wide concern.
4 steps from start to impact.
Identify target application using fast-uri for host validation
fast-uri.parse() or fast-uri.normalize() on untrusted URLs and then checks the parsed .host property against a denylist or allowlist. This is an uncommon pattern — most fast-uri consumers inherit it through AJV for schema validation, not URL policy enforcement.- Target app uses fast-uri directly (not just transitively via AJV)
- App uses parsed host for security-critical decisions
- Vast majority of fast-uri installs are transitive AJV/Fastify deps
- Application must expose a URL input surface to attacker
Craft URL with percent-encoded hostname octets
http://%49nternal.corp.com/secret. Because fast-uri lowercases *before* decoding, the parsed host becomes Internal.corp.com instead of internal.corp.com. No tooling beyond a browser or curl is needed.- Knowledge of the target's denylist/allowlist entries
- Ability to submit crafted URLs to the application
- DNS is case-insensitive, so the resolved IP is identical regardless of casing
- Many apps use
.toLowerCase()on host before comparison, negating the bug
Bypass case-sensitive host check
parsed.host === 'internal.corp.com', the percent-encoded variant yields Internal.corp.com which does not match. For a denylist, this means the blocked host is reachable; for an allowlist, the legitimate host is rejected (DoS, not escalation). The attacker's interesting path is denylist bypass leading to SSRF.- Host comparison must be case-sensitive (===, not case-folded)
- Denylist scenario for offensive value
- Downstream HTTP clients (Node fetch, axios) resolve DNS case-insensitively — the actual connection target is unchanged
- Modern SSRF frameworks typically validate at the IP/socket level, not hostname string level
Reach internal resource via SSRF
- Vulnerable app proxies or fetches the attacker-supplied URL server-side
- Internal target has exploitable resources
- Cloud metadata services (169.254.169.254) are IP-based, not hostname-based — this bug doesn't help
- Most SSRF-hardened apps validate resolved IPs, not just hostnames
The supporting signals.
| In-the-wild exploitation | None observed. Not listed in CISA KEV. No known campaigns or threat actor usage. |
|---|---|
| Proof of concept | Advisory includes a trivial one-liner: parse('//%41.com').host → A.com. No weaponized PoC or exploit tool exists. |
| EPSS score | Not yet scored (published 2026-09-15). Expected to land in the bottom quartile given AC:H and library-level impact. |
| KEV status | Not listed. No federal mandate to patch. |
| CVSS vector | CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N — network-reachable but high complexity, low confidentiality and integrity impact, no availability impact. |
| Affected versions | fast-uri < 2.4.7, 3.0.0–3.1.7, 4.0.0–4.1.4 |
| Fixed versions | 2.4.7, 3.1.8, 4.1.5 |
| Exposure / install base | ~100M weekly npm downloads, but 303 direct dependents. Overwhelmingly consumed transitively via AJV and Fastify for schema validation, not URL security. |
| Disclosure date | 2026-09-15 (coordinated disclosure via GitHub Security Advisory GHSA-hrr3-gc8f-f4qj) |
| Reporter / maintainer | Disclosed via Fastify project; patch authored by Matteo Collina (@mcollina). |
noisgate verdict.
The single most decisive factor is the vanishingly narrow exploitable usage pattern: over 99% of fast-uri's ~100M weekly installs consume it as a transitive AJV/Fastify dependency for JSON Schema resolution, where hostname casing is security-irrelevant. Exploitation requires a bespoke application that directly uses fast-uri's parsed host for case-sensitive security decisions on attacker-controlled URLs — a three-gate chain that collapses the reachable population to a rounding error of the install base.
Why this verdict
- Transitive-only exposure for 99%+ of installs: fast-uri's 100M weekly downloads are driven by AJV and Fastify, which use it for
$refresolution in JSON Schema — not for URL allowlist/denylist enforcement. The bug is unreachable in this dominant usage path. - Three-gate exploitation chain: Attacker needs (1) an app calling
fast-uri.parse()on user input, (2) case-sensitive host comparison for access control, (3) server-side fetch of the resulting URL. Each gate eliminates most of the remaining population. - DNS case-insensitivity neutralizes impact: Even when the parsed host string differs in casing, DNS resolution is case-insensitive. The downstream HTTP client connects to the same IP regardless, meaning the bug only matters when the *string comparison* is the security boundary — not the network connection.
- Role multiplier: fast-uri is a utility library, not a component that defines a deployment role. It appears in CI/CD (build-time via Webpack/schema-utils), in API servers (Fastify), and in validation layers (AJV). In none of these canonical roles does the host-casing bug grant domain takeover, fleet compromise, or supply-chain pivot. The blast radius is limited to the individual application's URL-validation logic — host-level, not fleet-level. No high-value role floor applies.
- No exploitation evidence, no weaponized tooling: Zero in-the-wild activity, no KEV listing, no researcher PoC beyond the advisory's one-liner example.
Why not higher?
Upgrading to MEDIUM or above would require either active exploitation evidence or a broader blast radius. The bug does not enable RCE, privilege escalation, or data exfiltration on its own — it is a logic inconsistency that *might* weaken a host check in a bespoke application. The vendor's own AC:H rating already acknowledges the difficulty, and the real-world narrowing (transitive-only usage, DNS case-insensitivity) compounds further.
Why not lower?
A full IGNORE verdict would be inappropriate because the bug *is* real and *could* weaken SSRF protections in the small population of apps that directly use fast-uri for URL policy. The advisory example is trivially reproducible, the fix is a one-line npm update, and defenders should still track it for completeness.
What to do — in priority order.
- Lowercase parsed hosts before comparison — Add
.toLowerCase()to any code path that comparesfast-uri.parse().hostagainst allow/deny lists. This neutralizes the bug entirely and takes minutes to deploy. No deadline pressure — this is a LOW finding, so treat as backlog hygiene. - Validate at the IP/socket layer for SSRF — Instead of comparing hostname strings, resolve the URL to an IP address and validate against blocked CIDR ranges (e.g., 169.254.0.0/16, 10.0.0.0/8, 127.0.0.0/8). This is the correct SSRF defense regardless of this CVE.
- Upgrade fast-uri to patched version — Run
npm audit fixor pin fast-uri to >=2.4.7 / >=3.1.8 / >=4.1.5. Since this is a LOW verdict, fold into your next scheduled dependency update cycle within 365 days per the noisgate remediation SLA.
- WAF URL-decoding rules — Most WAFs decode percent-encoding in paths and query strings but do not normalize hostname casing. The bypass operates at the hostname level before the WAF's purview.
- Content Security Policy (CSP) — CSP is a browser-side control; this vulnerability is server-side URL parsing. CSP headers have no effect on server-side host validation logic.
Crowdsourced verification payload.
Run on any machine with Node.js installed. Execute: node /tmp/check-fast-uri-cve-2026-86472.js. No special privileges required.
#!/usr/bin/env node
// CVE-2026-86472 - fast-uri inconsistent host case normalization
// Outputs: VULNERABLE / PATCHED / UNKNOWN
try {
const fastUri = require('fast-uri');
const pkg = require('fast-uri/package.json');
const version = pkg.version;
// Test: parse a URL with percent-encoded uppercase octet in host
const result = fastUri.parse('//%41.example.com');
const host = result.host || '';
// In patched versions, host should be lowercased to 'a.example.com'
// In vulnerable versions, host will be 'A.example.com'
if (host === 'A.example.com' || host === '%41.example.com') {
console.log('VULNERABLE');
console.log('fast-uri version: ' + version);
console.log('Parsed host: ' + host + ' (expected: a.example.com)');
process.exit(1);
} else if (host.toLowerCase() === 'a.example.com') {
console.log('PATCHED');
console.log('fast-uri version: ' + version);
console.log('Parsed host: ' + host);
process.exit(0);
} else {
console.log('UNKNOWN');
console.log('fast-uri version: ' + version);
console.log('Unexpected host value: ' + host);
process.exit(2);
}
} catch (e) {
if (e.code === 'MODULE_NOT_FOUND') {
console.log('UNKNOWN');
console.log('fast-uri is not installed in this project.');
process.exit(2);
}
console.log('UNKNOWN');
console.log('Error: ' + e.message);
process.exit(2);
}If you remember one thing.
fast-uri.parse() on user-supplied URLs and checks the host for access control, add .toLowerCase() to that comparison as an immediate one-line fix. For the 99%+ of your estate where fast-uri is a transitive AJV/Fastify dependency used for JSON Schema resolution, this CVE is functionally inert — no action needed beyond the routine npm audit fix in your next maintenance window. Per the noisgate remediation SLA for LOW findings, there is no hard deadline; document the rationale and move on to higher-priority work.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.