Leaving a bracket dangling so the bouncer reads a different name on your ID than the one the taxi driver sees
CVE-2026-84394 is a parser-differential bug in fast-uri, the dependency-free RFC 3986 URI toolbox maintained under the Fastify GitHub org. When a URI authority contains an opening bracket [ without a matching closing bracket ] — e.g. http://@127.0.0.1/path — fast-uri's parse() neither validates it as an IPv6 literal nor flags an error. It returns the malformed string as the host with error: undefined. Node's built-in WHATWG URL constructor and HTTP clients like fetch or axios resolve that same string to a *different* host. The bug affects fast-uri ≤ 2.4.5, ≤ 3.1.6, and ≤ 4.1.3, and is fixed in 2.4.6, 3.1.7, and 4.1.4. Published September 2, 2026 under [GHSA-58mr-gqgx-xq4g.
The vendor rates this HIGH at CVSS 7.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N). That score is technically correct *if* the application uses fast-uri output to make a host-based security decision (SSRF denylist, redirect allowlist, proxy routing) and then hands the original URL to a different parser for the actual HTTP request. In practice, the overwhelming majority of fast-uri's 100M+ weekly npm downloads arrive transitively through @fastify/ajv-compiler → ajv for JSON Schema $ref resolution and format: "uri" validation, where host extraction has zero security relevance. The exploitable population — apps that use fast-uri directly as an SSRF gate — is a tiny fraction of the install base, which makes the vendor score misleadingly broad.
4 steps from start to impact.
Identify a fast-uri–gated endpoint
fast-uri.parse(), and uses the returned host field to enforce a security policy (e.g., blocking requests to 127.0.0.1, 169.254.169.254, or internal RFC 1918 ranges). This is the architectural prerequisite — without it, the parser differential has no security impact.- Application exposes an endpoint accepting user-controlled URLs
- Application uses fast-uri (not Node's URL) for host-based security filtering
- Most fast-uri consumers use it transitively via Ajv for schema validation, not URL security
- Developers building SSRF filters more commonly use Node's built-in URL or well-known libraries like
new URL()
Craft a bracket-malformed URI
[ but omits the closing ], such as http://[@169.254.169.254/latest/meta-data/. fast-uri returns the raw malformed string as the host (e.g., [@169.254.169.254) without setting an error, so the denylist check compares against a string that does not match 169.254.169.254.- fast-uri version ≤ 2.4.5 / ≤ 3.1.6 / ≤ 4.1.3
- Trivial to construct — no tooling needed beyond a browser or curl
fast-uri.parse() output used in security-sensitive comparisons could flag this pattern.Bypass the host-based policy
http.request, fetch, axios, etc.), which applies WHATWG URL parsing and resolves the host differently — landing on the attacker's intended target (e.g., the cloud metadata endpoint or an internal service).- Application passes the *original* URL (not fast-uri's serialized output) to the HTTP client
- Downstream HTTP client resolves the malformed host to the attacker's intended destination
- If the app re-serializes via fast-uri before making the request, the malformed host propagates and the HTTP client may reject it
- WAFs or API gateways upstream may independently block requests to metadata IPs
Reach internal or metadata target
- Target server has network access to sensitive internal endpoints
- Cloud metadata service is not hardened with IMDSv2 or equivalent
- AWS IMDSv2 requires a PUT-based token exchange that most SSRF payloads cannot perform
- Network segmentation and metadata endpoint hardening are increasingly standard
require('fast-uri') or import … from 'fast-uri' where the parsed host field is used in denylist/allowlist comparisons, proxy routing, or redirect validation. If found, those are your priority targets. Most Ajv/Fastify transitive consumers are not affected and can follow the standard 365-day remediation window.new URL(input).hostname which applies WHATWG normalization consistent with what fetch and http.request use. This eliminates the parser differential entirely, regardless of fast-uri version.npm audit fix or npm update. Per noisgate remediation SLA for MEDIUM, complete within 365 days.- WAF URL normalization rules — most WAFs inspect the inbound HTTP request URL, not the URL *parameter value* that the application will later use for an outbound SSRF request. The malformed bracket is in the parameter payload, not the request path.
- Input length or character-set restrictions — the payload (
[@169.254.169.254) uses only ASCII characters and is short enough to fit any reasonable URL parameter limit. - Upgrading Fastify alone — if your application directly imports fast-uri (not via Ajv), upgrading Fastify won't change the fast-uri version your code resolves. Check your direct dependency tree.
The supporting signals.
| In-the-wild exploitation | No evidence. Not listed in CISA KEV. No known campaigns or threat actor usage as of 2026-09-03. |
|---|---|
| Proof of concept | No weaponized PoC published. The advisory describes the payload pattern ([@127.0.0.1) which is trivially reproducible but requires a vulnerable application architecture to have security impact. |
| EPSS score | Not yet scored — CVE published September 2, 2026; EPSS data typically lags 1–2 weeks for new entries. |
| KEV status | Not listed. No CISA KEV entry as of 2026-09-03. |
| 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 or interaction needed, high integrity impact, no confidentiality or availability impact. |
| Affected versions | fast-uri ≤ 2.4.5 (v2 branch), ≤ 3.1.6 (v3 branch), ≤ 4.1.3 (v4 branch) |
| Fixed versions | 2.4.6, 3.1.7, 4.1.4 — parse() now returns URI host is malformed. for any host containing a bracket that is not a valid [IPv6] literal. |
| Exposure footprint | fast-uri has ~100M+ weekly npm downloads, but the vast majority arrive as a transitive dependency of @fastify/ajv-compiler for JSON Schema validation. Direct use as a URL security filter is a small fraction of the install base. |
| Disclosure date | 2026-09-02 (reserved 2026-09-01) |
| Reporter | YashvantHange (reporter); mcollina (remediation developer); UlisesGascon (reviewer). Part of a broader fast-uri host-confusion audit that produced CVE-2026-6322, CVE-2026-13676, CVE-2026-16221, CVE-2026-18446, and others. |
Sources.
Why this verdict
- Narrow exploitable architecture: The bug only has security impact when an application uses
fast-uri.parse()for host-based security decisions (SSRF denylist, redirect allowlist) AND passes the original URL to a different parser for the HTTP request. Most of the 100M+ weekly downloads flow through Ajv for JSON Schema validation, where host extraction is irrelevant. - No RCE, integrity-only impact: The CVSS vector confirms C:N/I:H/A:N — the worst case is SSRF to internal endpoints. There is no code execution, privilege escalation, or data destruction path from this bug alone.
- Modern cloud hardening reduces blast radius: AWS IMDSv2, GCP metadata concealment, and Azure IMDS token requirements mean the classic SSRF-to-credential-theft chain is increasingly blocked even when the parser bypass succeeds.
- Role multiplier: fast-uri is a utility library, not a product that canonically occupies a high-value infrastructure role. In theory, a Fastify-based API gateway or webhook proxy *could* use fast-uri for SSRF filtering, putting it in a network-edge-adjacent role — but this is an atypical deployment pattern, not the default. The blast radius in the typical role (schema validation in a web app) is zero. In the worst-case atypical role (SSRF gate in an API proxy), the blast radius is tenant-level SSRF, not fleet or domain compromise.
- No exploitation evidence or weaponized tooling: No KEV listing, no known campaigns, no public exploit tooling. The payload pattern is trivial to construct but requires identifying a vulnerable application architecture first.
Why not higher?
To warrant HIGH, the exploitable population would need to be a significant fraction of the install base, or the impact would need to reach beyond SSRF to RCE or domain-level compromise. Neither condition holds: the vast majority of fast-uri consumers are Ajv schema validators with no security-relevant host parsing, and the impact ceiling is bounded SSRF without code execution.
Why not lower?
Despite the narrow exploitable population, the parser differential is real, trivially triggered (no authentication, no user interaction), and the SSRF bypass pattern is a well-understood attack class. Any application that *does* use fast-uri as an SSRF gate is fully exposed with no friction once the architectural prerequisite is met. The bug also sits in a library with enormous reach, so even a small percentage of direct consumers represents a non-trivial number of applications.
Crowdsourced verification payload.
Run on any host with Node.js ≥ 14 and npm/yarn available, in the root of the project you want to check. No special privileges needed. Example: bash check_fast_uri.sh /path/to/your/project
#!/usr/bin/env bash
# check_fast_uri.sh — Detect CVE-2026-84394 (fast-uri unclosed bracket host confusion)
# Usage: bash check_fast_uri.sh [/path/to/project]
# Exit codes: 0 = PATCHED/NOT_PRESENT, 1 = VULNERABLE, 2 = UNKNOWN
set -euo pipefail
PROJECT_DIR="${1:-.}"
if [ ! -d "$PROJECT_DIR/node_modules" ]; then
echo "UNKNOWN — no node_modules found in $PROJECT_DIR. Run npm install first."
exit 2
fi
# Find all installed fast-uri versions
FOUND=0
VULN=0
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
FOUND=1
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" -lt 4 ] || ([ "$minor" -eq 4 ] && [ "$patch" -le 5 ]); then
vulnerable=true
fi
;;
3)
if [ "$minor" -lt 1 ] || ([ "$minor" -eq 1 ] && [ "$patch" -le 6 ]); then
vulnerable=true
fi
;;
4)
if [ "$minor" -lt 1 ] || ([ "$minor" -eq 1 ] && [ "$patch" -le 3 ]); then
vulnerable=true
fi
;;
1|0)
vulnerable=true
;;
esac
if [ "$vulnerable" = true ]; then
echo "VULNERABLE — fast-uri $version found at $pkg_json"
VULN=1
else
echo "PATCHED — fast-uri $version found at $pkg_json"
fi
done < <(find "$PROJECT_DIR/node_modules" -path '*/fast-uri/package.json' -not -path '*/node_modules/.cache/*' 2>/dev/null)
if [ "$FOUND" -eq 0 ]; then
echo "PATCHED — fast-uri not found in $PROJECT_DIR (not a dependency)."
exit 0
fi
if [ "$VULN" -gt 0 ]; then
exit 1
else
exit 0
fi