The patch for the front-door lock introduced a second keyhole that opens the same door
CVE-2026-75899 is a double percent-decoding flaw in fast-uri's normalize() and resolve() functions. The fix for CVE-2026-6322 (encoded authority-delimiter confusion) introduced a second decode pass. When an attacker supplies a double-encoded hostname like %256c%256f%2563%2561%256c%2568%256f%2573%2574 (which decodes to %6c%6f%63%61%6c%68%6f%73%74, then to localhost), fast-uri's normalization pipeline decodes it twice and emits the final hostname as localhost. Affected version ranges are 2.4.1–2.4.4, 3.1.2–3.1.5, and 4.0.0–4.1.2 — note these are exactly the versions that shipped the CVE-2026-6322 fix. Patched versions are 2.4.5, 3.1.6, and 4.1.3, published August 23, 2026.
The vendor rates this HIGH at CVSS 7.5. That score describes the theoretical maximum: unauthenticated, network-reachable, no user interaction, high integrity impact. In isolation those vector components are accurate. But the score assumes the consuming application uses fast-uri's normalize() or resolve() output for security-relevant URL allowlist/denylist enforcement *before* making outbound requests. The overwhelming majority of fast-uri's 115K+ npm dependents consume it through AJV and Fastify for JSON Schema $ref resolution — a context where double-decoded hostnames don't translate into SSRF. The vendor severity is therefore overstated for most deployments and noisgate downgrades it to MEDIUM.
4 steps from start to impact.
Identify a target application using fast-uri for URL policy enforcement
normalize() before checking the hostname against an allowlist or denylist. This is the critical precondition — without it, the double-decode has no security impact. The attacker can fingerprint by submitting double-encoded hostnames and observing redirect or fetch behavior.- Application uses fast-uri normalize() or resolve() for URL validation
- Application runs affected version (2.4.1–2.4.4, 3.1.2–3.1.5, or 4.0.0–4.1.2)
- Most fast-uri consumers use it via AJV/Fastify for schema $ref resolution, not URL policy enforcement
- No public enumeration of which apps use fast-uri for allowlist checks
Craft double-encoded hostname payload
localhost (cloud metadata endpoint), the attacker sends http://%2531%2536%2539%252e%2532%2535%2534%252e%2531%2536%2539%252e%2532%2535%2534/latest/meta-data/. After fast-uri's first decode the hostname still looks encoded; after the second decode it resolves to 169.254.169.254. The payload is trivial to construct — no tooling beyond a URL encoder is needed.- Attacker can supply arbitrary URL to the application's input
- Payload construction is trivial — no exploit code or tooling required
%25 sequences in the Host component can flag this pattern.Bypass allowlist / denylist check
%256c%256f%2563%2561%256c%2568%256f%2573%2574) and does not match it against blocked entries like localhost or 169.254.169.254. The double-decoded output from fast-uri is then used to construct the outbound HTTP request, which reaches the attacker-chosen host.- Application performs allowlist check BEFORE fast-uri normalization fully decodes the hostname
- Application uses the normalized output for the actual outbound request
- Applications that check the hostname AFTER normalization, or that use a different URL parser for the outbound request, are not affected
- Node.js built-in URL parser does not double-decode, so apps using new URL() for the fetch step may not be vulnerable
Achieve SSRF to internal resources
- Internal services or metadata endpoints are reachable from the application server
- Cloud providers increasingly require IMDSv2 (hop-limited, token-required), which blocks basic SSRF to metadata
- Network segmentation and egress filtering limit lateral reach
%25 in the host component — The advisory's recommended workaround. Add input validation that rejects any URL whose host portion contains %25 (the encoding of %) before passing it to fast-uri. This eliminates the double-decode vector at the input boundary. Deploy within the noisgate remediation SLA of 365 days, or sooner if your application uses fast-uri for URL policy enforcement.npm audit fix or pin the patched version in package.json / package-lock.json. This is the remediation action — target the 365-day noisgate remediation SLA window.new URL() (WHATWG parser) rather than fast-uri's normalize(). The built-in parser does not double-decode. This architectural change eliminates the entire class of fast-uri parsing confusion bugs.- WAF rules matching
localhostor169.254.169.254in the URL — the double-encoded payload will not match these literal strings at the WAF layer; it only resolves after fast-uri processes it. - Upgrading to the CVE-2026-6322 fix versions (3.1.2–3.1.5) — those versions *are* the affected versions for this CVE. You need 3.1.6+, 2.4.5+, or 4.1.3+.
- Generic SSRF denylist in the application — if the denylist check runs against the fast-uri normalized output, the double-decoded hostname *will* match. But if it runs against the raw input, the encoded hostname bypasses it. The issue is check ordering, not the denylist itself.
The supporting signals.
| In-the-Wild Exploitation | No known exploitation. Not listed on CISA KEV. No reports of active campaigns targeting this CVE as of 2026-08-24. |
|---|---|
| Proof of Concept | The advisory itself contains a working PoC: normalize('http://%256c%256f%2563%2561%256c%2568%256f%2573%2574/') → http://localhost/. Trivial to adapt. No weaponized exploit repos identified yet (disclosed 2026-08-23). |
| EPSS Score | Not yet scored (disclosed <48 hours ago). Sibling CVE-2026-6322 has EPSS 0.00029 (~low probability). Expect similar or lower given narrower affected population. |
| KEV Status | Not listed. No CISA KEV entry. |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N — Network/no-auth/no-interaction. Integrity-only impact. Scope unchanged (no pivot across trust boundaries at the library level). |
| Affected Versions | 2.4.1–2.4.4, 3.1.2–3.1.5, 4.0.0–4.1.2. These are the versions that shipped the CVE-2026-6322 fix — the patch *caused* this regression. |
| Fixed Versions | 2.4.5, 3.1.6, 4.1.3 (all released 2026-08-23). |
| Scanning / Exposure | fast-uri has ~465M weekly npm downloads and 115K+ dependents. The vulnerable population is a subset: only those on the specific affected version ranges who applied the CVE-2026-6322 fix. Shodan/Censys are not relevant (library, not a service). |
| Disclosure Date | 2026-08-23 (coordinated disclosure via GitHub Security Advisory GHSA-fph4-wmhf-6fwf). |
| Credit | Reported by NotAFlightRisk. Fix by mcollina (Matteo Collina, Fastify lead). Reviewed by UlisesGascon. |
Sources.
Why this verdict
- Narrow exploitable use case: fast-uri is overwhelmingly consumed for JSON Schema $ref resolution via AJV and Fastify. URL allowlist enforcement — the only context where this double-decode matters — is a minority pattern among dependents.
- Regression-only population: Only versions 2.4.1+, 3.1.2+, and 4.0.0+ are affected — these are specifically the versions that shipped the CVE-2026-6322 fix. Users who never patched CVE-2026-6322 are *not* affected by this CVE (though they have the earlier bug).
- No scope change: CVSS Scope is Unchanged. The SSRF impact is bounded by the consuming application's network position and egress controls. Modern cloud environments with IMDSv2 and network segmentation further limit blast radius.
- Role multiplier: fast-uri is a utility library, not a canonically high-value-role component. It could theoretically appear in any role, but the exploitable scenario (URL allowlist bypass) is most relevant in web application tiers acting as SSRF gateways. In a CI/CD pipeline or identity provider context, fast-uri would typically be used for schema validation, not URL policy — the high-value role chain does not succeed in practice. Blast radius: host-level (the application server that makes the forged request), not fleet or domain scale.
- No exploitation evidence: No KEV listing, no known campaigns, no weaponized exploit repos. Disclosed <48 hours ago with a same-day patch available.
Why not higher?
Upgrading to HIGH would require either active exploitation evidence or a broader exploitable population. The vulnerability is in a library consumed overwhelmingly for schema validation, not URL security enforcement. The attack chain requires a specific application architecture (fast-uri normalize → allowlist check → outbound fetch using normalized output) that represents a minority of real-world deployments. Without evidence that a significant fraction of the 115K dependents use fast-uri this way, HIGH is not warranted.
Why not lower?
Dropping to LOW would undercount the risk for the subset of applications that *do* use fast-uri for URL validation. For those apps, the exploit is trivial (no auth, no interaction, simple double-encoding), the payload is already documented in the advisory, and SSRF to cloud metadata or internal services is a proven high-impact outcome. The 465M weekly download footprint means even a small percentage of vulnerable use cases is a non-trivial absolute number.
Crowdsourced verification payload.
Run this on any machine with Node.js installed. It checks whether the installed fast-uri version is in the affected range. Invoke with: bash check_cve_2026_75899.sh /path/to/your/project. No special privileges required.
#!/usr/bin/env bash
# check_cve_2026_75899.sh — Detect CVE-2026-75899 in fast-uri
# Usage: bash check_cve_2026_75899.sh [project_dir]
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -euo pipefail
PROJECT_DIR="${1:-.}"
if [ ! -d "$PROJECT_DIR/node_modules/fast-uri" ]; then
echo "UNKNOWN — fast-uri not found in $PROJECT_DIR/node_modules/"
exit 2
fi
VERSION=$(node -e "console.log(require('$PROJECT_DIR/node_modules/fast-uri/package.json').version)" 2>/dev/null)
if [ -z "$VERSION" ]; then
echo "UNKNOWN — could not read fast-uri version"
exit 2
fi
echo "Detected fast-uri version: $VERSION"
# Check if version is in affected ranges using node semver comparison
RESULT=$(node -e "
const v = '$VERSION';
const [major, minor, patch] = v.split('.').map(Number);
let affected = false;
// 2.4.1 - 2.4.4
if (major === 2 && minor === 4 && patch >= 1 && patch <= 4) affected = true;
// 3.1.2 - 3.1.5
if (major === 3 && minor === 1 && patch >= 2 && patch <= 5) affected = true;
// 4.0.0 - 4.1.2
if (major === 4 && (minor === 0 || (minor === 1 && patch <= 2))) affected = true;
console.log(affected ? 'VULNERABLE' : 'PATCHED');
" 2>/dev/null)
if [ "$RESULT" = "VULNERABLE" ]; then
echo "VULNERABLE — fast-uri $VERSION is affected by CVE-2026-75899"
echo "Upgrade to 2.4.5, 3.1.6, or 4.1.3+"
exit 1
elif [ "$RESULT" = "PATCHED" ]; then
echo "PATCHED — fast-uri $VERSION is not in the affected range"
exit 0
else
echo "UNKNOWN — could not determine vulnerability status"
exit 2
fi