Like putting a trap door in a hallway that 99% of tenants never walk down
CVE-2026-76172 affects fast-uri, a dependency-free RFC 3986 URI toolbox for Node.js maintained under the Fastify project. The bug is in the normalize() function: it decodes percent-encoded characters in the *scheme* component using the legacy global unescape() and serializes the result back as raw characters without re-escaping or validating. A crafted input like %2f%2fevil.example:/pwn initially parses without an authority component, but after normalization becomes //evil.example:/pwn, which reparses with evil.example as the host. Affected version ranges span v2.x ≥ 2.3.1 < 2.4.5, v3.x ≥ 3.0.0 < 3.1.6, and v4.x ≥ 4.0.0 < 4.1.3. Fixed in 2.4.5, 3.1.6, and 4.1.3 respectively. Published August 23, 2026.
The vendor rates this HIGH 7.5 — and the CVSS vector (AV:N/AC:L/PR:N/UI:N) is technically accurate *for the subset of apps that call normalize() on attacker-controlled URLs and use the output for security-relevant host checks*. But that vector dramatically overstates real-world exposure. fast-uri pulls ~83 million weekly npm downloads, almost entirely as a transitive dependency of ajv (the JSON Schema validator), where it resolves developer-authored $id and $ref URIs — not user-supplied URLs. The population of applications that (a) call fast-uri.normalize() directly on untrusted input, (b) use the result for allowlist enforcement, and (c) then pass it to a *different* URL parser for actual fetching is a narrow slice of that install base. The vendor severity assumes a worst-case integration pattern that most consumers don't exhibit.
4 steps from start to impact.
Attacker identifies target using fast-uri for URL validation
normalize() before dispatching the URL via Node's built-in URL or fetch(). This is the critical prerequisite — without this specific integration pattern, the chain is dead.- Target application accepts user-controlled URLs
- Application calls fast-uri normalize() on those URLs
- Normalized output feeds into a host allowlist check
- Most fast-uri consumers inherit it via ajv and never call normalize() directly
- Applications using the WHATWG URL API for both validation and fetching are immune because the parser is consistent
Craft percent-encoded scheme payload
%2f%2fevil.example:/pwn. Before normalization, this parses as a relative-path reference or an opaque scheme with no authority. The PoC is trivial — no tooling beyond a text editor is required.- Knowledge of the allowlisted domain(s) to bypass
- Attacker must know or guess the specific allowlist entries in the target application
%2f%2f in scheme position can flag this pattern.Normalization flips the URI structure
normalize() calls unescape() on the scheme, converting %2f%2f to //. The serialized output //evil.example:/pwn now has an authority component pointing to evil.example. The allowlist check that ran *before* this transformation saw the original benign-looking input and passed it.- Application validates host BEFORE normalize(), then uses post-normalize output for fetching
- If the application validates AFTER normalize(), the confusion is visible and blocked
- If the application uses the same parser end-to-end, no desync occurs
SSRF or open redirect to attacker-controlled host
http.request(), fetch(), or a redirect response. The request lands on evil.example instead of the intended allowlisted host. Impact ranges from open redirect (integrity) to SSRF against internal infrastructure if the allowlist was guarding outbound server-side requests to cloud metadata endpoints or internal APIs.- Application fetches or redirects using the normalized URL
- Cloud metadata SSRF requires the Node.js process to run in a cloud environment with an accessible metadata endpoint (169.254.169.254)
- Most modern cloud providers (AWS IMDSv2, GCP) require hop-limited tokens that resist blind SSRF
The supporting signals.
| In-the-wild exploitation | Not observed. No KEV listing, no vendor or threat-intel reports of active exploitation as of 2026-08-24. |
|---|---|
| Proof of concept | Trivial PoC in the advisory itself: %2f%2fevil.example:/pwn → normalize → //evil.example:/pwn. No weaponized exploit tooling published. Reporter: YashvantHange. |
| EPSS | Not yet scored (published 2026-08-23, <48 hours old). Expect low-to-moderate EPSS given library-level bug with conditional exploitability. |
| KEV status | Not listed. CISA KEV catalog does not include this CVE. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N — Network-accessible, no privileges, no interaction, integrity-only impact. No confidentiality or availability impact scored. |
| Affected versions | v2.x: ≥ 2.3.1, < 2.4.5 · v3.x: ≥ 3.0.0, < 3.1.6 · v4.x: ≥ 4.0.0, < 4.1.3 |
| Fixed versions | 2.4.5, 3.1.6, 4.1.3 (all released 2026-08-23) |
| Install base / exposure | ~83 million weekly npm downloads. 292 direct dependents. Overwhelmingly pulled transitively via ajv → @fastify/ajv-compiler → fastify. Shodan/Censys not applicable (library, not a network service). |
| Disclosure date | 2026-08-23 (coordinated via GitHub Security Advisory GHSA-jqff-g426-hqxp) |
| Reporter / org | YashvantHange (reporter), mcollina (remediation), UlisesGascon (reviewer). Part of a batch of 4 fast-uri advisories published the same day. |
noisgate verdict.
The single most decisive downgrade factor is conditional exploitability as a library dependency: the vulnerable normalize() code path is only security-relevant when the consuming application explicitly calls it on attacker-controlled URLs for host-policy decisions, a pattern that excludes the vast majority of the ~83M weekly download base (which inherits fast-uri via ajv for developer-authored schema URIs). Without that specific integration, the bug has zero security impact regardless of network exposure.
Why this verdict
- Conditional code-path reachability: fast-uri is overwhelmingly consumed via ajv for JSON Schema
$refresolution, where URIs are developer-controlled constants. The vulnerablenormalize()function must be called on *attacker-supplied* input to be exploitable — a pattern that applies to a small fraction of the install base. - Validate-then-use desync required: Exploitation demands a two-parser desync: the application validates the host using one representation and then fetches using the post-normalization representation. Applications using a single consistent URL parser (e.g., WHATWG
URLthroughout) are immune. - Role multiplier: fast-uri runs in Node.js application tiers. (a) *Low-value role* — dev tooling, build scripts using ajv for config validation: chain does not succeed (no attacker-controlled URLs). (b) *Typical role* — API server using Fastify with ajv schema validation: chain does not succeed (ajv does not pass user input to fast-uri normalize). (c) *High-value role* — API gateway or SSO proxy performing URL allowlist validation via fast-uri before redirect/fetch: chain succeeds, blast radius is application-level SSRF/open-redirect (tenant-scoped, not fleet/domain). This does NOT reach the domain-takeover / fleet-compromise / supply-chain threshold. Verdict floor remains MEDIUM.
- No exploitation evidence: Zero KEV, zero in-the-wild reports, zero weaponized PoC tooling. The advisory is <48 hours old but the PoC is trivially simple — if mass exploitation were viable, we would expect rapid uptake, which has not materialized.
- Integrity-only impact: CVSS confirms C:N/I:H/A:N. Even in the worst case (SSRF), the attacker redirects a request — they do not directly exfiltrate data from the vulnerable process or crash it.
Why not higher?
Upgrading to HIGH would require evidence that a meaningful fraction of fast-uri consumers call normalize() on untrusted URLs for security-critical decisions. The dominant consumption path (ajv schema resolution) is not attacker-reachable. There is no KEV listing, no active exploitation, and the blast radius even in the worst-case high-value role (API gateway SSRF) is tenant-scoped, not fleet-scale. The role-multiplier floor check does not trigger because the affected component (a URI parsing library) is not canonically a high-value-role component — it is infrastructure plumbing whose impact is entirely application-dependent.
Why not lower?
Dropping to LOW would understate the risk for the subset of applications that *do* use fast-uri for URL validation on untrusted input. The PoC is trivial, exploitation requires no authentication or interaction, and SSRF against cloud metadata or internal services carries real consequences. The 83M weekly download footprint means even a small percentage of vulnerable integration patterns represents thousands of applications. MEDIUM correctly captures this conditional-but-real risk.
What to do — in priority order.
- Pin and upgrade fast-uri across your lockfiles — Run
npm audit fixor explicitly bump to 2.4.5 / 3.1.6 / 4.1.3. This is a pure-JavaScript dependency update with no native bindings — risk of regression is low. Target completion within the noisgate 365-day remediation window for MEDIUM. - Audit direct fast-uri usage in application code — Search your codebase for
require('fast-uri')orimport.*fast-uriand check whethernormalize(),resolve(), orequal()are called on user-supplied URL strings. If not found, the vulnerability is not reachable in your deployment and remediation priority drops further. - Use WHATWG URL consistently for security decisions — If your application validates URLs before fetching or redirecting, use Node's built-in
new URL()for both validation and consumption. This eliminates the two-parser desync that the exploit relies on. - Block percent-encoded slashes in scheme position at the WAF — A WAF rule rejecting
%2for%2Fin the URI scheme component (before the first colon) catches this and related normalization confusion payloads.
- Upgrading Fastify alone does not help if your lockfile still resolves a vulnerable fast-uri version — check the transitive dependency, not just the framework.
- CSP headers do not mitigate server-side SSRF; they only constrain browser-side resource loading.
- Network segmentation around the Node.js process reduces SSRF blast radius but does not fix the URL confusion itself — open-redirect remains possible.
Crowdsourced verification payload.
Run this on any host with Node.js installed. Invoke: bash check-cve-2026-76172.sh /path/to/project. No special privileges required — it reads node_modules and package-lock.json.
#!/usr/bin/env bash
# check-cve-2026-76172.sh — detect vulnerable fast-uri versions
# Usage: bash check-cve-2026-76172.sh /path/to/node/project
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN
set -euo pipefail
PROJECT_DIR="${1:-.}"
VULN=0
if [ ! -d "$PROJECT_DIR/node_modules" ]; then
echo "UNKNOWN — no node_modules found in $PROJECT_DIR"
exit 2
fi
# Find all installed fast-uri versions
while IFS= read -r pkg_json; do
version=$(node -e "console.log(require('$pkg_json').version)" 2>/dev/null || echo "unknown")
if [ "$version" = "unknown" ]; then
continue
fi
major=$(echo "$version" | cut -d. -f1)
minor=$(echo "$version" | cut -d. -f2)
patch=$(echo "$version" | cut -d. -f3)
vulnerable=false
case "$major" in
2)
if [ "$minor" -eq 3 ] && [ "$patch" -ge 1 ]; then vulnerable=true; fi
if [ "$minor" -eq 4 ] && [ "$patch" -lt 5 ]; then vulnerable=true; fi
if [ "$minor" -eq 4 ] && [ "$patch" -ge 5 ]; then vulnerable=false; fi
;;
3)
if [ "$minor" -eq 0 ]; then vulnerable=true; fi
if [ "$minor" -eq 1 ] && [ "$patch" -lt 6 ]; then vulnerable=true; fi
;;
4)
if [ "$minor" -eq 0 ]; then vulnerable=true; fi
if [ "$minor" -eq 1 ] && [ "$patch" -lt 3 ]; then vulnerable=true; fi
;;
esac
if [ "$vulnerable" = true ]; then
echo "VULNERABLE — fast-uri $version found at $pkg_json"
VULN=1
else
echo "PATCHED — fast-uri $version at $pkg_json"
fi
done < <(find "$PROJECT_DIR/node_modules" -path '*/fast-uri/package.json' -not -path '*/node_modules/.cache/*' 2>/dev/null)
if [ "$VULN" -eq 1 ]; then
exit 1
elif [ "$VULN" -eq 0 ]; then
# Check if any fast-uri was found at all
count=$(find "$PROJECT_DIR/node_modules" -path '*/fast-uri/package.json' -not -path '*/node_modules/.cache/*' 2>/dev/null | wc -l)
if [ "$count" -eq 0 ]; then
echo "UNKNOWN — fast-uri not found in node_modules"
exit 2
fi
echo "PATCHED — all fast-uri instances are on fixed versions"
exit 0
fiIf you remember one thing.
fast-uri imports that call normalize() on user-supplied URLs — if you find none, this is a backlog item only. Second, bump fast-uri to 2.4.5 / 3.1.6 / 4.1.3 via npm audit fix within the noisgate remediation SLA of 365 days for MEDIUM-severity findings. There is no mitigation SLA for MEDIUM — go straight to the 365-day remediation window. If you do find direct usage on untrusted input, treat it as a targeted HIGH and patch within 30 days while adding a WAF rule to block %2f in scheme position as an interim control.Sources
- GHSA-jqff-g426-hqxp — GitHub Security Advisory
- fast-uri on npm — download stats and dependents
- CWE-177: Improper Handling of URL Encoding (MITRE)
- fast-uri security advisories — full list
- GHSA-v39h-62p7-jpjc — related host confusion via percent-encoded authority delimiters
- fast-uri on Snyk — vulnerability database
- CISA Known Exploited Vulnerabilities Catalog
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.