← Back to Feed CACHED · 2026-10-18 15:33:49 · CACHE_KEY CVE-2026-16221
CVE-2026-16221 · CWE-436 · Disclosed 2026-07-19

fast-uri host confusion via literal backslash authority delimiter

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

A bouncer checking IDs with the wrong alphabet while the doorman behind him reads perfectly

CVE-2026-16221 is a URI-parser interpretation conflict in fast-uri, the RFC 3986 toolbox maintained under the Fastify org. Versions 2.3.1–2.4.2, 3.0.0–3.1.3, and 4.0.0–4.1.0 do not treat a literal backslash (U+005C) as an authority delimiter. Node.js's WHATWG URL parser — the engine behind fetch(), undici, and the native http/https modules — normalizes \ to / for special schemes (http, https, ws, wss, ftp, file). The result: http://evil.com\@allowed.com resolves to host allowed.com in fast-uri but host evil.com in Node's parser. If an application uses fast-uri to enforce a host allowlist and then hands the same string to fetch(), the attacker bypasses the check and steers the request to an arbitrary destination — including cloud metadata endpoints like 169.254.169.254.

The vendor rates this HIGH 7.5 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N), and the vector math is technically correct *if you assume the app uses fast-uri as its SSRF policy gate*. That assumption dramatically overstates real-world exposure. fast-uri pulls ~90–120 million weekly npm downloads, but the overwhelming majority arrive as a transitive dependency of ajv (the JSON Schema validator), which uses fast-uri solely for $id / $ref schema resolution — a code path that never touches outbound HTTP allowlists. The subset of applications that (a) call fast-uri.parse() directly for host-policy enforcement, (b) then pass the identical string to Node's WHATWG parser, and (c) expose that endpoint to untrusted input is genuinely small. No exploitation in the wild, no indexed PoC, EPSS at the 16th percentile (0.25%), and not on CISA KEV. The vendor HIGH is fair for the narrow pattern that's actually vulnerable, but for fleet-wide prioritization it overstates urgency.

"Library-level parser confusion rarely sits on the SSRF-filter hot path most teams assume it does."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Identify a fast-uri–gated SSRF surface

The attacker must find a Node.js service that accepts a user-supplied URL, parses it with fast-uri.parse() to enforce a host allowlist or denylist, and then forwards the same raw string to Node's native URL consumer (fetch(), undici, http.request()). This is a specific architectural pattern, not a default behavior of any framework.
Conditions required:
  • Target application uses fast-uri directly for URL policy enforcement
  • User-supplied URL reaches the fast-uri parse path
Where this breaks in practice:
  • The vast majority of fast-uri installations are transitive through ajv for JSON Schema $ref resolution — they never touch HTTP request URLs
  • Fastify's own routing does not use fast-uri for security-gating outbound requests
  • Most SSRF-protection libraries in the Node ecosystem (e.g., ssrf-req-filter, private-ip) use Node's native URL, not fast-uri
Detection/coverage: Software Composition Analysis (SCA) tools (Snyk, npm audit, Socket.dev, Trivy) flag the vulnerable version. npm audit catches this automatically. No network-layer scanner can detect this — it is a code-level pattern.
STEP 02

Craft a backslash-confused URL

The attacker constructs a URL like http://evil.com\@allowed.com. fast-uri reads the backslash as part of the userinfo field, extracting host allowed.com. Node's WHATWG URL reads \ as a path separator after authority, extracting host evil.com. The payload is trivial — no encoding tricks, no race conditions, no heap gymnastics.
Conditions required:
  • Attacker can supply an arbitrary URL string to the vulnerable endpoint
Where this breaks in practice:
  • Some WAFs and input-validation layers may strip or reject raw backslashes in URL parameters, though this is not universal
  • URL-encoding the backslash (%5C) may or may not reproduce the bug depending on whether the application decodes before parsing
STEP 03

Bypass allowlist / SSRF filter

fast-uri returns allowed.com as the hostname. The application's allowlist check passes. The same raw URL string is then passed to fetch() or http.request(), where Node's parser resolves it to evil.com. The request leaves the application bound for the attacker's host or an internal target.
Conditions required:
  • The application does not re-parse the URL with the same parser that the HTTP client uses
  • The allowlist check relies exclusively on fast-uri's host extraction
Where this breaks in practice:
  • Applications that validate AND request using the same parser (e.g., both using new URL()) are immune
  • Defense-in-depth SSRF controls at the network layer (blocking RFC 1918, link-local, metadata IPs at the firewall/proxy) still stop the most damaging outcomes
Detection/coverage: Outbound request logging and anomaly detection can flag requests to unexpected hosts if the application logs the resolved destination.
STEP 04

Reach internal target or cloud metadata

With the SSRF filter bypassed, the attacker can steer the request to 169.254.169.254 (cloud metadata), 127.0.0.1, or any internal host. On cloud infrastructure, a successful metadata read can yield IAM credentials, enabling lateral movement. On-prem, the attacker can scan internal services.
Conditions required:
  • The application's IAM role or network position grants access to sensitive internal endpoints
  • Cloud metadata endpoint is not blocked by IMDSv2 enforcement or network policy
Where this breaks in practice:
  • AWS IMDSv2 (token-required) blocks SSRF-driven metadata reads from simple GET requests
  • GCP and Azure metadata endpoints require specific headers that the application's HTTP client may not set
  • Network-level egress controls (security groups, NACLs, service mesh policies) can block lateral requests
Detection/coverage: Cloud provider metadata access logs (AWS CloudTrail for IMDS, GCP audit logs) can detect unexpected metadata queries. GreyNoise has no specific tag for this CVE.
03 · Compensating Control

1
MEDIUM 5.3→IGNORE 0.0
SEVERITY REDUCED
Refresh lockfile to pull fast-uri ≥3.1.4 (or ≥4.1.1, ≥2.4.3) — Run npm audit fix or npm update fast-uri in every Node.js project. Since ajv's semver range (^3.0.1) already accepts the patched version, this is a lockfile-only change with zero code modifications. Recommend jumping to 3.1.8+ or 4.2.2 to also cover sibling CVEs (CVE-2026-18446 and later). As a MEDIUM verdict, there is no noisgate mitigation SLA — go straight to the 365-day remediation window, though the fix is trivial enough to deploy in days.
2
MEDIUM 5.3→LOW 3.1
SEVERITY REDUCED
Enforce IMDSv2 on all cloud compute instances — AWS IMDSv2 requires a PUT-based token exchange before metadata reads, which blocks SSRF-driven GET requests to 169.254.169.254. GCP and Azure metadata endpoints require specific headers (Metadata-Flavor: Google, Metadata: true). This eliminates the highest-impact SSRF outcome (credential theft from metadata) even if the parser confusion is exploited. Deploy within existing cloud hardening cycles.
3
MEDIUM 5.3→LOW 2.5
SEVERITY REDUCED
Block outbound requests to RFC 1918, link-local, and metadata IPs at the network layer — Configure security groups, NACLs, or egress proxy rules to deny application-tier outbound traffic to 169.254.169.254/32, 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16 unless explicitly required. This provides defense-in-depth against any SSRF bypass, not just this CVE.
4
MEDIUM 5.3→IGNORE 0.0
SEVERITY REDUCED
Use the same URL parser for policy and execution — If your application validates URLs before making outbound requests, ensure the policy check and the HTTP client use the same parser — ideally Node's native new URL(). Never parse with fast-uri for security decisions and then pass the raw string to fetch(). This architectural fix eliminates the entire class of parser-confusion SSRF.
What doesn't work
  • WAF rules blocking backslashes in URL parameters — The backslash may arrive URL-encoded (%5C), and many legitimate URLs contain backslashes in query strings or fragments. The false-positive rate makes this impractical at scale.
  • Upgrading ajv alone — ajv's package.json does not pin a specific fast-uri patch version; it uses ^3.0.1. The ajv package version is irrelevant — you must update the *resolved* fast-uri version in your lockfile.
  • Input sanitization / stripping backslashes — The vulnerability is in the parser, not in input handling. Stripping backslashes from URLs can break legitimate functionality and may be bypassed by double-encoding or alternate representations.
04 · Intelligence Metadata

The supporting signals.

In-the-Wild ExploitationNone observed. Not listed on CISA KEV. No GreyNoise tags. No campaign reporting from Proofpoint, Mandiant, or CrowdStrike references this CVE. The vulnerability is library-level and not directly scannable from the network.
Proof-of-Concept AvailabilityNo public PoC indexed. Checked pocindex.io, ExploitDB, Nuclei templates, and GitHub repos named after the CVE — no dedicated exploit code. The GHSA advisory includes the example payload http://evil.com\@allowed.com which is effectively a one-liner PoC for anyone who reads the advisory. Weaponization is trivial but target identification is hard.
EPSS Score0.00254 (0.25%) — 16th percentile. This places it well below the exploitation-likely threshold. The model reflects the absence of in-the-wild activity and PoC tooling.
CISA KEV StatusNot listed. No known active exploitation meets KEV inclusion criteria.
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N — Network-accessible, low complexity, no privileges, no interaction, unchanged scope, high integrity impact, no confidentiality or availability impact. Snyk independently scored 8.7 using a slightly different assessment. The vector assumes the vulnerable code path is reachable, which is the key question for real deployments.
Affected Versionsfast-uri 2.3.1–2.4.2, 3.0.0–3.1.3, 4.0.0–4.1.0. The 3.x line is the most prevalent in the wild because ajv pins ^3.0.1.
Fixed Versions2.4.3, 3.1.4, 4.1.1. All are semver-compatible with ajv's ^3.0.1 constraint, so a lockfile refresh (npm update fast-uri or npm audit fix) is sufficient — no ajv version bump required.
Installed Base / Exposurefast-uri has ~88–122 million weekly npm downloads and 323 direct dependents on npm. The primary consumer is ajv (JSON Schema validator), which itself has 200M+ weekly downloads. However, ajv uses fast-uri for $id/$ref schema resolution, not for HTTP URL policy enforcement. The *reachable vulnerable surface* is a small fraction of the installed base.
Disclosure & CreditDisclosed 2026-07-19 by researcher rampage0010. Remediation reviewed by mcollina (Matteo Collina, Fastify lead) and UlisesGascon. Published as GHSA-v2hh-gcrm-f6hx.
Related CVEsCVE-2026-18446 (GHSA for backslash *authority introducer* variant, fixed in 2.4.4/3.1.5/4.1.2) is a sibling bug in the same parser. Teams patching CVE-2026-16221 should jump straight to 3.1.8+ or 4.2.2 to cover all known fast-uri parsing issues.

Sources.

  1. GitHub Advisory — GHSA-v2hh-gcrm-f6hx
  2. NVD — CVE-2026-16221
  3. Snyk — SNYK-JS-FASTURI-18021349
  4. GitLab Advisory Database — fast-uri CVE-2026-16221
  5. npm — fast-uri package
  6. CVE Feed — CVE-2026-16221 Detail
  7. OSV — GHSA-v2hh-gcrm-f6hx
05 · The Call

Final Verdict
↓ DOWNGRADED to MEDIUM (5.3/10)

Why this verdict

  • Reachability gap is the dominant factor. The CVSS vector assumes an attacker can reach the fast-uri→WHATWG desync code path. In practice, ~95%+ of the 88–122M weekly installs arrive via ajv for JSON Schema $ref resolution — a path that never gates outbound HTTP requests. The number of applications calling fast-uri.parse() directly for SSRF/allowlist enforcement is vanishingly small.
  • No exploitation signal compounds the reachability narrowing. Zero in-the-wild exploitation, no KEV listing, EPSS at 16th percentile (0.25%), and no weaponized PoC tooling indexed on pocindex.io or GitHub. The advisory's example payload is trivial to reproduce but requires a target with the specific dual-parser pattern.
  • Network-layer SSRF defenses cap the blast radius. Even where the vulnerable code path exists, AWS IMDSv2, GCP/Azure metadata header requirements, egress security groups, and network-level RFC 1918 blocking independently prevent the most damaging SSRF outcomes. These controls are standard in any enterprise cloud deployment.
  • Role multiplier: fast-uri is a utility library, not infrastructure. It does not run on domain controllers, hypervisors, CI/CD runners, or network edge appliances. It runs inside Node.js application processes. (a) *Low-value role*: dev tooling, build scripts, test suites — the schema-validation path is not exploitable. (b) *Typical role*: line-of-business Node.js API server using ajv for request validation — fast-uri's $ref resolution is not in the HTTP-policy hot path; chain does not succeed. (c) *High-value role*: a Node.js API gateway or proxy that uses fast-uri directly to validate outbound URLs before forwarding — the chain succeeds and SSRF could reach cloud metadata or internal services. However, this represents a tiny fraction (<1%) of the fast-uri installed base. The blast radius is application-scoped (single service's IAM role), not domain/fleet/supply-chain scoped. This does not meet the HIGH floor threshold because the affected component is not canonically a high-value-role component, and the high-value deployment pattern represents well under 1% of installs.
  • The fix is frictionless. npm audit fix or npm update fast-uri resolves the issue without breaking changes. ajv's ^3.0.1 constraint already accepts the patched 3.1.4+. This is a lockfile-only remediation.

Why not higher?

The vendor's HIGH 7.5 would be appropriate if fast-uri were commonly used as the SSRF policy gate in production Node.js services. It is not — it is overwhelmingly consumed as a transitive dependency of ajv for JSON Schema validation, a code path that is not exploitable via this CVE. The absence of any exploitation evidence, any indexed PoC tooling, and any KEV listing after three months of disclosure reinforces that the theoretical attack surface has not translated to practical risk. The high-value role (API gateway using fast-uri for URL policy) represents far less than 1% of the installed base, keeping this below the HIGH floor.

Why not lower?

The attack payload is trivially simple (http://evil.com\@allowed.com), requires no authentication or user interaction, and the advisory text itself serves as a de facto PoC. For the minority of applications that DO use fast-uri for URL policy enforcement, exploitation is straightforward and the SSRF impact is real. The massive installed base (88M+ weekly downloads) means even a small percentage of vulnerable-pattern deployments could represent thousands of applications. Dropping to LOW would understate the risk for those specific deployments.

06 · Verification

Crowdsourced verification payload.

Run this script from the root of any Node.js project (where package.json lives) on the auditor's workstation or in CI. Requires node (v14+) on PATH. No elevated privileges needed. Invoke: bash check_cve_2026_16221.sh /path/to/project

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check_cve_2026_16221.sh — Detect vulnerable fast-uri (CVE-2026-16221)
# Usage: bash check_cve_2026_16221.sh [project_dir]
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -euo pipefail

PROJDIR="${1:-.}"

if [ ! -d "$PROJDIR/node_modules" ]; then
  echo "[UNKNOWN] No node_modules found in $PROJDIR — run npm install first or specify the project root."
  exit 2
fi

# Find all installed fast-uri versions
FOUND=0
VULN=0

while IFS= read -r pkgjson; do
  VERSION=$(node -e "try{console.log(require('$pkgjson').version)}catch(e){console.log('err')}" 2>/dev/null)
  if [ "$VERSION" = "err" ] || [ -z "$VERSION" ]; then
    continue
  fi
  FOUND=1
  MAJOR=$(echo "$VERSION" | cut -d. -f1)
  MINOR=$(echo "$VERSION" | cut -d. -f2)
  PATCH=$(echo "$VERSION" | cut -d. -f3 | cut -d- -f1)

  IS_VULN=0
  case "$MAJOR" in
    2)
      # Vulnerable: 2.3.1 through 2.4.2
      if [ "$MINOR" -eq 3 ] && [ "$PATCH" -ge 1 ]; then IS_VULN=1; fi
      if [ "$MINOR" -eq 4 ] && [ "$PATCH" -le 2 ]; then IS_VULN=1; fi
      ;;
    3)
      # Vulnerable: 3.0.0 through 3.1.3
      if [ "$MINOR" -eq 0 ]; then IS_VULN=1; fi
      if [ "$MINOR" -eq 1 ] && [ "$PATCH" -le 3 ]; then IS_VULN=1; fi
      ;;
    4)
      # Vulnerable: 4.0.0 through 4.1.0
      if [ "$MINOR" -eq 0 ]; then IS_VULN=1; fi
      if [ "$MINOR" -eq 1 ] && [ "$PATCH" -eq 0 ]; then IS_VULN=1; fi
      ;;
  esac

  if [ "$IS_VULN" -eq 1 ]; then
    echo "[VULNERABLE] fast-uri $VERSION found at: $(dirname "$pkgjson")"
    VULN=1
  else
    echo "[PATCHED] fast-uri $VERSION found at: $(dirname "$pkgjson")"
  fi
done < <(find "$PROJDIR/node_modules" -path '*/fast-uri/package.json' -not -path '*/node_modules/*/node_modules/*/node_modules/*' 2>/dev/null)

if [ "$FOUND" -eq 0 ]; then
  echo "[UNKNOWN] fast-uri not found in $PROJDIR/node_modules — package may not be a dependency."
  exit 2
fi

if [ "$VULN" -eq 1 ]; then
  echo ""
  echo "Remediation: run 'npm update fast-uri' or 'npm audit fix' to pull >=3.1.4"
  exit 1
else
  echo ""
  echo "All fast-uri instances are patched."
  exit 0
fi