A URL parser that reads `еxample.com` (Cyrillic 'е') as `example.com` and waves it through
fast-uri is a high-performance RFC 3986 URI parser used heavily by the Fastify ecosystem and by ajv-formats for JSON Schema uri/uri-reference validation. Versions 2.3.1–3.1.2 and 4.0.0 skip proper Unicode/IDN canonicalization on HTTP-family URLs, so a hostname containing homoglyphs, mixed scripts, or unnormalized Unicode forms can be parsed to a *different* effective authority than what an allowlist check sees. The library returns a host string that passes a string-equality or suffix check against trusted.com but, when later resolved by Node's DNS resolver or fetch(), points somewhere else entirely.
Vendor-rated HIGH (7.5, CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N) treats this as a network-reachable integrity break. That score is *defensible for the worst-case consumer* (an SSRF allowlist, an OAuth redirect_uri validator, a webhook URL verifier) but overstates the typical case — most fast-uri callers parse URIs for routing or schema validation, not for security decisions. The bug is real and worth fixing, but the blast radius is bounded by what each downstream caller does with the parsed host.
4 steps from start to impact.
Identify a fast-uri-backed trust boundary
redirect_uri, image proxies, SSRF-guarded fetchers, JSON Schema format: uri validators. Confirms (via fingerprinting or open-source code review) that fast-uri 2.3.1–3.1.2 or 4.0.0 sits behind the validator, typically via Fastify or ajv-formats.- Target exposes a URL-accepting endpoint
- Validator depends on
fast-uri.parse().hostfor an allowlist decision
- Many Fastify users don't pin fast-uri directly — version is whatever Fastify ships
- Most consumers use the parser for routing, not for trust decisions
Craft a homoglyph/mixed-script hostname
xn--xample-2of.com for Cyrillic е+xample.com) or chooses an unnormalized Unicode form that fast-uri returns verbatim but Node's resolver or libcurl maps elsewhere. Tools like homoglyph-attack or manual Punycode crafting are sufficient.- Attacker controls a lookalike domain or a domain whose Punycode form differs from its NFC form
- Downstream resolver applies its own IDN normalization that disagrees with fast-uri's output
- Registrars increasingly block mixed-script registrations
- Browsers and many resolvers refuse mixed-script IDN by default
Submit the URL through the validator
host string that string-matches the allowlist (api.victim.com, *.victim.com, etc.), and the app accepts the request as trusted.- The allowlist uses string equality, suffix match, or regex over
parsed.host - No second-pass canonicalization (e.g.
URLWHATWG parser) is applied
- Defensive code often double-parses with Node's built-in
URLwhich normalizes IDN - Allowlists that resolve to IP and compare CIDR ranges are immune
Server fetches the attacker's host
fetch(url), axios.get(url), or hands the URL to an OAuth flow. The HTTP client re-resolves the IDN via its own (correct) canonicalization and hits the attacker-controlled origin — exfiltrating an OAuth code, leaking a webhook secret, or pivoting to internal services if the lookalike resolves to RFC1918.- Server-side fetch follows the validated URL with credentials or tokens
- Or: an OAuth
codeis redirected to the attacker authority
- Egress proxies with explicit allowlists block unknown destinations
- OAuth providers increasingly require exact-match redirect_uri registration
The supporting signals.
| In-the-wild exploitation | None observed as of 2026-06-30. No GreyNoise tags, no honeypot reports. |
|---|---|
| PoC availability | No public PoC in the GHSA advisory at disclosure; homoglyph/IDN confusion is a *well-documented class* (see BlackHat 2019 HostSplit) so weaponization is trivial for a competent researcher. |
| EPSS | Not yet scored (CVE disclosed 2026-06-29); class-based estimate <1% / <50th percentile. |
| KEV status | Not listed. Library-level parser bugs rarely make KEV unless tied to a named campaign. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N — network, no auth, integrity-only. The I:H assumes the parser output drives a security decision. |
| Affected versions | fast-uri 2.3.1 through 3.1.2, and 4.0.0 |
| Fixed versions | Upgrade to 3.1.3 (3.x line) or 4.0.1 (4.x line) — confirm against the GHSA at release. |
| Downstream blast radius | Pulled transitively by Fastify and ajv-formats; npm monthly downloads in the tens of millions. Most consumers do not use it as a security boundary. |
| CWE | CWE-436 — Interpretation Conflict (parser vs. resolver disagree on what the host *is*). |
| Disclosure | 2026-06-29, coordinated via GitHub Security Advisory on the fast-uri repo. |
noisgate verdict.
Downgraded from vendor HIGH to MEDIUM because the decisive factor is *exploitability gated on downstream usage* — the bug only reaches I:H impact when a caller uses fast-uri.parse().host as a trust-boundary check, which is a minority pattern. The affected component is a parser library with no high-value-role multiplier (it is not an IdP, hypervisor, CA, or kernel-mode agent), so the role-multiplier floor does not pull the verdict back to HIGH.
Why this verdict
- Friction — caller-dependent impact: the I:H rating only materializes if
parsed.hostis the *sole* basis for an allowlist decision. Most Fastify routes use fast-uri for routing/normalization, not authorization. - Friction — defense-in-depth common: OAuth providers enforce exact-match
redirect_uri, egress proxies enforce destination allowlists, and many apps double-parse with WHATWGURLwhich normalizes IDN correctly. - Role multiplier: fast-uri is a *parser library*, not a high-value-role component. Even on an IdP or webhook gateway, the chain ends at *one* misrouted request, not domain or fleet compromise — no floor escalation applies.
- Easy fix: patch is a minor-version bump with no breaking API changes; SCA tools will surface it within days.
Why not higher?
Not HIGH because the chain requires a specific downstream pattern (host-string allowlist with no secondary canonicalization) that is not the dominant usage of the library. There is no KEV listing, no active exploitation, and no role-multiplier (parser libs don't carry the blast radius of an IdP or hypervisor).
Why not lower?
Not LOW because the bug *does* enable SSRF / open-redirect / OAuth-code theft in real applications that exist today, the affected library is deployed at enormous scale through Fastify, and weaponization for a targeted application is straightforward once a researcher identifies a vulnerable validator.
What to do — in priority order.
- Inventory transitive fast-uri usage — Run
npm ls fast-uri(orpnpm why fast-uri/yarn why fast-uri) across every Node service. Within the 365-day MEDIUM remediation window, prioritize services that accept user-supplied URLs (webhooks, OAuth, SSRF-guarded fetchers). - Add a second-pass WHATWG URL canonicalization — Wherever you validate a URL for trust purposes, re-parse with Node's built-in
new URL(input)and compare.hostname(which applies correct IDN/Punycode normalization) against the allowlist *in addition to* any fast-uri-based check. Deploy in your normal change window — there is no mitigation SLA for MEDIUM. - Enforce egress allowlists from app tiers — App servers handling user-supplied URLs should egress through an HTTP proxy with an explicit destination allowlist. This neutralizes IDN-confusion SSRF independent of the parser fix.
- Upgrade Fastify to a version that pins the patched fast-uri — Once Fastify ships a release pinning fast-uri ≥ 3.1.3 / ≥ 4.0.1, bump Fastify as part of normal dependency hygiene rather than per-service overrides.
- WAF regex blocking non-ASCII characters in URL parameters — breaks legitimate i18n traffic and misses Punycode (
xn--) forms that are pure ASCII. - Domain-name-only allowlists without secondary canonicalization — that is precisely the pattern this CVE exploits.
- Network segmentation alone — SSRF to *external* attacker infrastructure still works; segmentation only blocks the internal-pivot variant.
Crowdsourced verification payload.
Run on any Node project's repo root (developer workstation or CI runner) where a package-lock.json / pnpm-lock.yaml / yarn.lock is present. No special privileges required. Example: ./check-fast-uri.sh /path/to/repo.
#!/usr/bin/env bash
# noisgate: detect vulnerable fast-uri (CVE-2026-13676)
# Vulnerable: 2.3.1 <= v <= 3.1.2, or v == 4.0.0
# Fixed: 3.1.3+, 4.0.1+
set -u
REPO="${1:-.}"
cd "$REPO" || { echo "UNKNOWN: cannot cd $REPO"; exit 2; }
if ! command -v node >/dev/null 2>&1; then
echo "UNKNOWN: node not installed"; exit 2
fi
if ! command -v npm >/dev/null 2>&1; then
echo "UNKNOWN: npm not installed"; exit 2
fi
# Collect every installed fast-uri version (handles transitive)
VERSIONS=$(npm ls fast-uri --all --json 2>/dev/null \
| node -e "let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{try{const j=JSON.parse(d);const out=new Set();(function walk(n){if(!n||typeof n!=='object')return;if(n.dependencies){for(const [k,v] of Object.entries(n.dependencies)){if(k==='fast-uri'&&v.version)out.add(v.version);walk(v);}}})(j);process.stdout.write([...out].join('\\n'));}catch(e){}}")
if [ -z "$VERSIONS" ]; then
echo "PATCHED: fast-uri not present in dependency tree"
exit 0
fi
vuln=0
while IFS= read -r v; do
[ -z "$v" ] && continue
echo "found fast-uri $v"
node -e "const semver=(a,b)=>a.split('.').map(Number).reduce((r,n,i)=>r||(n-(b.split('.').map(Number)[i]||0)),0);const v='$v';if((semver(v,'2.3.1')>=0 && semver(v,'3.1.2')<=0) || v==='4.0.0'){process.exit(1);}else{process.exit(0);}"
if [ $? -eq 1 ]; then
echo " -> VULNERABLE"
vuln=1
else
echo " -> patched"
fi
done <<< "$VERSIONS"
if [ $vuln -eq 1 ]; then
echo "VULNERABLE"
exit 1
fi
echo "PATCHED"
exit 0
If you remember one thing.
npm ls fast-uri across your Node estate and tag every service that accepts user-supplied URLs (webhooks, OAuth redirect_uri, SSRF-guarded fetchers, image proxies). The noisgate mitigation SLA for MEDIUM has no hard deadline — go straight to the noisgate remediation SLA of ≤ 365 days to bump fast-uri to 3.1.3 / 4.0.1 (or upgrade Fastify once it pins the fix). For the subset of services that use parsed.host as a trust-boundary check, treat those individually as HIGH and add a WHATWG URL second-pass canonicalization within 30 days as belt-and-suspenders. Do *not* burn weekend cycles patch-and-praying every Fastify service — the vast majority of fast-uri callers never reach the I:H impact path.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.