The bouncer and the door guy read the guest list in different languages, and the attacker slips through the gap
fast-uri's parse() and normalize() accept a literal backslash (\) as part of the host component rather than treating it as a delimiter. Node's built-in URL, browsers, and any WHATWG-compliant client normalize \ to / inside the authority. So a payload like http://trusted.example.com\@evil.tld/path is read by fast-uri as host=trusted.example.com\@evil.tld (or accepted as trusted.example.com depending on code path), while the downstream HTTP client that actually makes the request sees host=evil.tld. This is a classic parser differential — the same string means two different things to two different parsers in the same pipeline. Affected: fast-uri ≤ 3.1.1 (companion issue to CVE-2026-6321/6322); fixed in 3.1.2+.
The vendor's HIGH (7.5, C:N/I:H/A:N) reflects the correct impact class — this is an integrity bug, not RCE, not info disclosure of the parser itself. But calling it 7.5 flat undersells the *reach*: fast-uri is pulled transitively by ajv-formats → ajv → basically every JSON-schema-validating Node service on earth, plus Fastify's own request pipeline. On the other hand, it *oversells* the deterministic exploit rate: fast-uri has to be positioned as a security gate (SSRF allowlist, OAuth redirect_uri validator, webhook egress filter), not just a format check. Net: HIGH is fair. We hold it there.
4 steps from start to impact.
Identify a fast-uri-gated URL input
redirect_uri, webhook registration, SSRF-restricted image/PDF/URL fetchers, Location builders, server-side link previews. Fingerprinting looks for fastify response headers or JSON error messages leaking ajv / fast-uri in stack traces. Public dependency graphs on GitHub (package-lock.json, pnpm-lock.yaml) directly confirm fast-uri usage.- Target application accepts a URL field from the attacker
- Application uses fast-uri (directly or via
ajv-formatsuri/uri-reference) to validate that URL
- Some apps only use fast-uri for format validation, not for host allowlisting — in that case there is nothing to bypass
- Apps behind egress proxies (Zscaler, Squid with ACLs) still fail closed at the network layer
Craft a backslash-differential URL
https://allowed.example.com\@attacker.tld/, https://allowed.example.com\.attacker.tld/, or userinfo-style https://attacker.tld\@allowed.example.com/ variations. Public PoC repos for the sibling CVE-2026-6321/6322 already ship differential fuzzing harnesses (fast-uri vs new URL()) that produce backslash variants trivially.- Attacker can supply the raw URL without server-side percent-encoding of
\first
- Any middleware that URL-encodes the input before fast-uri sees it (Express's
qs, some API gateways) collapses\to%5Cand neutralizes the differential
\@ and \. inside URL parameters are cheap to write but not shipped by default in Cloudflare/Akamai/AWS WAF managed rulesets as of July 2026.Bypass the allowlist / SSRF filter
fastUri.parse(userUrl).host (or ajv format: 'uri' + a downstream .host check) and matches against an allowlist — the check passes because fast-uri reports the *allowed* host. Application then hands the raw string to fetch(), axios, undici, or http.request(), which reparse via WHATWG URL and hit the *attacker's* host.- fast-uri's output is trusted for a security decision, then the *original string* (not the reserialized fast-uri output) is passed downstream
- Apps that use fast-uri's serialized form (
.toString()on the parsed object) downstream are safer — but many apps validate then forward the original string
Land the impact — SSRF, open redirect, credential theft, or webhook exfil
- A meaningful sink exists downstream of the bypassed check
- IMDSv2 (required-token) blocks the metadata subcase
- Egress deny-by-default networks kill the SSRF subcase
- PKCE + exact-match redirect_uri on the IdP side blunts the OAuth subcase
UnauthorizedAccess:EC2/MetadataDNSRebind for the metadata pivot.The supporting signals.
| In-the-wild exploitation | No confirmed campaigns as of 2026-07-19. Sibling CVE-2026-6322 (percent-encoded delimiter) has PoC-only status; this backslash variant follows the same class. |
|---|---|
| PoC availability | Public differential fuzzer against fast-uri vs Node URL published alongside the GHSA advisory tree (fastify/fast-uri security advisories). Trivial to adapt for \. |
| EPSS | Sibling CVE-2026-6322 sits ~0.4% / ~65th percentile. Expect similar for this ID once FIRST scores it. |
| KEV status | Not listed. Library-level parser diff; CISA has historically not added Node URL-parsing bugs to KEV. |
| CVSS vector | AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N — network, no auth, no UI, integrity-only. Confidentiality/availability marked N is *technically* correct for the parser but understates downstream data leakage via SSRF sinks. |
| Affected versions | fast-uri ≤ 3.1.1 on npm. Directly used by Fastify and pulled transitively by ajv-formats (which ajv pulls in by convention). |
| Fixed version | fast-uri 3.1.2. No distro backports needed — this is npm-only. |
| Exposure population | fast-uri has ~35M weekly npm downloads per npmjs.com trends. Vast majority is transitive via ajv-formats. Population *actually using it as a security gate* is a small but non-trivial fraction of that. |
| Disclosed | 2026-07 (companion to the 2026-06 fast-uri advisory cluster CVE-2026-6321 / CVE-2026-6322). |
| Reporter | Same fastify security team + external researcher pipeline responsible for the GHSA-v39h-62p7-jpjc / GHSA-q3j6-qgpj-74h6 cluster. |
noisgate verdict.
HIGH is upheld because the single most decisive factor is supply-chain reach against a security-gate primitive — fast-uri is a transitive dep in a huge fraction of Node services and is the parser people reach for when validating URLs before an egress or redirect decision. Not raised to CRITICAL because exploitation is not deterministic — the app must actively use fast-uri's output for an allowlist check *and* forward the original string, not the reserialized form.
Why this verdict
- Parser differential with a WHATWG-compliant downstream is the archetypal SSRF/open-redirect enabler — the Orange Tsai / Snyk research corpus documents this class ending in credential theft on major SaaS platforms every year.
- Supply-chain footprint: fast-uri is transitively pulled by
ajv-formatswhich is pulled byajv, which is present in a majority of Fastify, NestJS, and JSON-Schema-validating Node services. Blast radius is *broad*, even if per-app exploitability is conditional. - Role multiplier — IdP / OAuth server: if fast-uri is validating
redirect_uriin a self-hosted OIDC provider, the chain ends in authorization code interception. That is a fleet-scale identity outcome and floors the verdict at HIGH. - Role multiplier — webhook / egress proxy: apps that let tenants register outbound webhook URLs and use fast-uri to enforce a host allowlist become SSRF pivots into the internal network. Same HIGH floor.
- Friction — conditional sink: apps that only use fast-uri for
ajvformat validation and never for allowlist decisions are unaffected in practice. This is why the verdict does not push to CRITICAL. - Friction — no auth needed but no confidentiality impact at the parser itself. CVSS C:N/I:H/A:N is honest about the primitive; the confidentiality damage happens at whatever sink the SSRF hits.
Why not higher?
Not CRITICAL because exploitation requires a specific application pattern (validate-with-fast-uri, then forward original string to a WHATWG client) rather than being a drop-in RCE. There is no active exploitation, no KEV listing, and no confirmed mass-scanning campaign. The bug is an *enabler* of downstream attacks, not the terminal impact on its own.
Why not lower?
Not MEDIUM because the deployment footprint is enormous (transitive dep of ajv-formats), the exploit is pre-auth and network-reachable, and the plausible chain terminates in OAuth code theft or internal SSRF on identity-role deployments. Parser-differential bugs in ubiquitous URL parsers have a long history of ending in real breaches, and treating this as MEDIUM would ignore that base rate.
What to do — in priority order.
- Bump fast-uri to ≥ 3.1.2 across all lockfiles — This is the actual fix. Run
npm ls fast-uri/pnpm why fast-uri/yarn why fast-uriin every repo, then use overrides (npmoverrides,pnpm.overrides,yarn resolutions) to force ≥3.1.2 even when transitive. Deploy within the noisgate HIGH mitigation window (30 days). - Reject backslash characters at the ingress layer — Add a WAF rule or middleware check that 400s any request whose URL/JSON body fields matching
format: uricontain a raw\in the authority portion. Cheap belt-and-braces while the dep bump rolls through CI. - Reserialize URLs with Node's built-in
URLbefore any host allowlist check — Never trustfastUri.parse(x).hostfor an allowlist decision. Donew URL(x).host(WHATWG) and compare that. This makes you resilient to *this* CVE and the entire parser-differential class. - Egress deny-by-default from any service that fetches user-supplied URLs — Squid/Envoy/Zscaler with a strict per-service allowlist blocks the SSRF terminal step even if the URL check is bypassed. If you already have this, this CVE drops to a redirect/webhook nuisance.
- For IdPs and OAuth clients: enforce exact-match redirect_uri and PKCE — Wildcard/prefix matching on redirect_uri is where this bug becomes an account takeover. Move to exact-match now regardless of whether you're on fast-uri.
- Blocklisting the
@character alone — the backslash variant sidesteps@-based filters because the confusion happens before userinfo parsing. - Regex-based URL validation on top of fast-uri — you're still relying on the same broken authority extraction; two wrongs don't make a right.
- EDR / host IDS — this is a Node userland library bug; nothing lands on the kernel, syscall telemetry sees a normal outbound HTTP.
- IMDSv2 alone — helps only the AWS-metadata subcase; does nothing for OAuth redirect_uri abuse or internal-service SSRF.
Crowdsourced verification payload.
Run on any developer workstation or CI runner from the root of a Node repo. Invoke as ./check-fast-uri.sh /path/to/repo (defaults to .). No elevated privileges required — needs npm or pnpm on PATH.
#!/usr/bin/env bash
# noisgate check for CVE-2026-16221 (fast-uri backslash host confusion)
# VULNERABLE : fast-uri <= 3.1.1 present in resolved tree
# PATCHED : fast-uri >= 3.1.2 only
# UNKNOWN : no lockfile / no Node project detected
set -u
ROOT="${1:-.}"
cd "$ROOT" 2>/dev/null || { echo "UNKNOWN: cannot cd to $ROOT"; exit 2; }
HAS_PKG=0
[ -f package.json ] && HAS_PKG=1
if [ "$HAS_PKG" -eq 0 ]; then
echo "UNKNOWN: no package.json at $ROOT"
exit 2
fi
# Try npm ls first, then pnpm, then yarn
VERSIONS=""
if command -v npm >/dev/null 2>&1; then
VERSIONS=$(npm ls fast-uri --all --parseable --long 2>/dev/null | awk -F: '{print $2}' | grep -Eo 'fast-uri@[0-9]+\.[0-9]+\.[0-9]+' | sort -u)
fi
if [ -z "$VERSIONS" ] && command -v pnpm >/dev/null 2>&1; then
VERSIONS=$(pnpm why fast-uri --json 2>/dev/null | grep -Eo '"fast-uri"[^}]*"version"[^,]*' | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+' | sort -u | sed 's/^/fast-uri@/')
fi
if [ -z "$VERSIONS" ] && [ -f package-lock.json ]; then
VERSIONS=$(grep -Eo '"fast-uri"\s*:\s*\{[^}]*"version"\s*:\s*"[0-9.]+"' package-lock.json | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+' | sort -u | sed 's/^/fast-uri@/')
fi
if [ -z "$VERSIONS" ]; then
echo "PATCHED: fast-uri not present in resolved dependency tree"
exit 0
fi
VULN=0
while IFS= read -r line; do
V=$(echo "$line" | sed 's/^fast-uri@//')
MAJ=$(echo "$V" | cut -d. -f1)
MIN=$(echo "$V" | cut -d. -f2)
PAT=$(echo "$V" | cut -d. -f3)
# Vulnerable: any 3.x <= 3.1.1, or any older major
if [ "$MAJ" -lt 3 ]; then VULN=1; echo " found $line (VULNERABLE)"; continue; fi
if [ "$MAJ" -eq 3 ] && [ "$MIN" -eq 0 ]; then VULN=1; echo " found $line (VULNERABLE)"; continue; fi
if [ "$MAJ" -eq 3 ] && [ "$MIN" -eq 1 ] && [ "$PAT" -lt 2 ]; then VULN=1; echo " found $line (VULNERABLE)"; continue; fi
echo " found $line (patched)"
done <<< "$VERSIONS"
if [ "$VULN" -eq 1 ]; then
echo "VULNERABLE: fast-uri <= 3.1.1 present. Bump to >= 3.1.2 via package overrides."
exit 1
fi
echo "PATCHED: all resolved fast-uri versions >= 3.1.2"
exit 0
If you remember one thing.
npm ls fast-uri (and pnpm/yarn equivalents) across every Node repo you own — including build-time and CI dependencies, not just runtime. Any repo showing fast-uri ≤ 3.1.1 gets a lockfile override to ≥ 3.1.2, opened as a PR by end of week. Per the noisgate mitigation SLA for HIGH, the WAF rule blocking backslashes in URL fields and the switch to new URL() for allowlist decisions must be deployed within 30 days; per the noisgate remediation SLA, the actual fast-uri version bump must be fully rolled out (all services rebuilt, deployed, and lockfile-audited) within 180 days. Prioritize services that (a) accept user-supplied URLs for outbound fetching, (b) validate OAuth redirect_uri, or (c) process tenant-registered webhooks — those are the ones where a parser differential ends in real damage, not just a lint finding.Sources
- GHSA-v39h-62p7-jpjc — fast-uri host confusion advisory (sibling CVE-2026-6322)
- GitHub Advisory Database — CVE-2026-6322
- GitLab GLAD — fast-uri CVE-2026-6322
- GitLab GLAD — fast-uri CVE-2026-6321 (path traversal companion)
- Eventus Security — fast-uri path traversal and host confusion writeup
- GHSA-4c8g-83qw-93j6 — fast-uri IDN canonicalization variant
- gh-aw-firewall issue tracking fast-uri advisory cluster
- Guzzle PSR-7 — parallel host confusion via authority reinterpretation (class reference)
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.