The bouncer checks IDs at the door but forgets they can be written in leetspeak
@fastify/http-proxy lets you mount an upstream service under a URL prefix and rewrite that prefix off before forwarding — e.g. /pub/* at the edge is stripped to /* at the backend. The bug is that Fastify's router decodes percent-encoded characters when matching the route, but the prefix-rewriting logic then operates on the raw, still-encoded request.url. A request to /%61pi/data matches the /api route for handler dispatch, but the rewriter can't find the literal string /api in the encoded URL, so no rewrite happens and the raw path is proxied through. Every version up to and including 11.5.0 is affected; 11.6.0 contains the fix.
Vendor scored this CVSS 10.0 Critical with Scope:Changed, C:H/I:H — reasoning that the flaw in the proxy directly compromises a separate upstream security zone. That framing is defensible for the worst-case deployment (a proxy fronting an unauthenticated internal admin API), but it treats every install as that worst case. In real fleets @fastify/http-proxy is often a routing convenience in front of already-authenticated services, and there is no RCE, no data destruction, and no lateral movement primitive on its own — the blast radius is *whatever the upstream chose to hide behind the prefix*. That is a HIGH, not a universal 10.
4 steps from start to impact.
Locate a Fastify proxy in front of a segmented upstream
@fastify/http-proxy — fingerprintable via Fastify's default headers, error bodies, or route probing. They confirm a prefix + rewritePrefix deployment by observing that /pub/anything returns 404s or upstream errors while /pub/ returns the upstream root.- Reachable HTTP endpoint running vulnerable @fastify/http-proxy ≤ 11.5.0
- Proxy uses
prefix+rewritePrefixto hide a subset of upstream paths
- Fastify's overall enterprise footprint is modest vs. Express/Nest
- Many deployments front the proxy with nginx/Cloudflare/WAF which may normalize encoded paths before Fastify sees them
Percent-encode the first character of the target path
/%61pi/admin (where %61 == a). Fastify's router decodes the path for route matching and dispatches to the /api proxy handler. The rewrite step then does a literal string search for /api inside request.url — which still contains the encoded form — and finds no match, so it forwards /%61pi/admin upstream unchanged. Trivial one-liner with curl or Burp Repeater; no exploit tool required.- No upstream WAF/CDN normalizing percent-encoding before Fastify
- Upstream tolerates encoded paths in routing (Node http, Express, most WSGI stacks do)
- Cloudflare, Akamai, AWS CloudFront, and nginx
merge_slashes/normalize_uriconfigs will canonicalize%61→abefore it reaches Node - Some upstreams reject encoded reserved-set characters
Reach paths the proxy was meant to hide
rewritePrefix scope — e.g. /internal/*, /admin/*, or sibling services on the same origin. If the upstream trusted the proxy to enforce path scoping (a common but risky pattern), the attacker now has unauthenticated access to those endpoints.- Upstream uses proxy prefix as its access-control boundary
- Sensitive endpoints exist on the same upstream origin
- Any upstream that enforces its own authn/authz on
/adminis unaffected - Zero-trust / mTLS between proxy and upstream neutralizes the impact
NOT startswith(/expected_prefix) filterChain to SSRF / data theft / admin action
/actuator, /metrics, /debug/pprof), cloud metadata proxies, admin APIs, or other tenant paths. No exploit toolkit needed — this is a raw HTTP request. No published PoC repo beyond the advisory description as of 2026-07-18.- Sensitive functionality reachable at the newly-accessible upstream paths
- Depends entirely on what the specific upstream exposes — many deployments have nothing juicy behind the prefix
The supporting signals.
| In-the-wild status | No known exploitation as of disclosure (2026-07-18). Not in CISA KEV. |
|---|---|
| Proof-of-concept | Advisory itself publishes the working payload (/%61pi/data). No standalone GitHub PoC repo yet; expect Nuclei template within days. |
| EPSS | Not yet scored (disclosure day). Comparable path-normalization bugs typically land 0.5–3% in the first week. |
| KEV status | Not listed |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N — network, unauth, Scope:Changed drives the 10.0 (impact crosses to the upstream service) |
| Affected versions | @fastify/http-proxy ≤ 11.5.0 (all releases). Prior to the 2022 npm scope migration, published as fastify-http-proxy. |
| Fixed version | 11.6.0 — no backports to older majors; upgrade is the only path |
| Exposure population | npm weekly downloads for @fastify/http-proxy are in the low hundreds of thousands — meaningful but 1–2 orders of magnitude below Express-based proxies. Public-internet Fastify fingerprints on Shodan number in the low tens of thousands. |
| Disclosure date | 2026-07-18 via GitHub Security Advisory GHSA-mx7v-qhg9-2mvv |
| Reporter / fix | Reported by tndud042713; patched by Matteo Collina (mcollina, Fastify lead maintainer) |
noisgate verdict.
Reassessed HIGH rather than the vendor's 10.0 because the single most decisive factor is *conditional blast radius*: the vulnerability only converts to real compromise when the upstream service trusts rewritePrefix as an authorization boundary — an insecure-by-design pattern present in a minority of deployments. Where that pattern exists the outcome is severe (unauthenticated access to hidden admin/internal paths), which is why we hold the line at HIGH and refuse to drop to MEDIUM.
Why this verdict
- Trivial exploitation: unauthenticated, network-reachable, one crafted URL. AC:L/PR:N/UI:N is accurate — this is the easiest possible bug to fire.
- Conditional impact: the actual damage requires the upstream to have delegated path-based authz to the proxy. Many mature deployments authenticate independently at the upstream — those installs see zero material impact.
- Role multiplier — API gateway in front of unauthenticated internal admin API: chain succeeds, blast radius is tenant-scale data / admin-action exposure. Plausible in perhaps 10–25% of
@fastify/http-proxyinstalls given the library's ergonomics encourage exactly this pattern. Floor stays at HIGH. - Role multiplier — proxy fronting a fully-authenticated upstream (JWT/session at backend): chain reaches new paths but hits auth walls. Blast radius = information disclosure at most.
- Role multiplier — hobby / low-traffic sites: irrelevant.
- No supply-chain or fleet-wide amplifier: this is a per-service bug, not a domain/PKI/EDR/CI compromise vector. That's the reason CRITICAL is not the floor.
- Upstream WAF / CDN often neutralizes: Cloudflare, CloudFront, Akamai, and default nginx configs normalize
%61→abefore Fastify sees it, silently killing the attack for a large fraction of internet-exposed instances.
Why not higher?
Not CRITICAL because @fastify/http-proxy is not canonically a high-value-role component (it's not an IdP, hypervisor, CA, backup, or kernel-mode agent), and the deployment share where the chain ends in fleet-scale or supply-chain compromise is not ≥10%. The CVSS 10.0 assumes universal Scope:Changed catastrophic impact; real installs vary from 'no impact' to 'tenant admin access' depending on upstream authz.
Why not lower?
Not MEDIUM because when the vulnerable pattern *is* present, the attack is unauthenticated, one-request, no user interaction, and directly yields access to intentionally-hidden endpoints. Same-day advisory PoC means weaponization is measured in hours, and downgrading below HIGH would fail teams whose gateways trust rewritePrefix — a non-trivial minority.
What to do — in priority order.
- Upgrade @fastify/http-proxy to ≥ 11.6.0 — Only real fix. Bump the dependency, run integration tests against your
prefixroutes, redeploy. For a HIGH verdict noisgate mandates rollout within 30 days; if the proxy is internet-exposed AND fronts an admin API, treat as CRITICAL and roll within 3 days. - Front the proxy with a normalizing edge (nginx, Cloudflare, CloudFront) — Configure the edge to decode and re-canonicalize the request path before forwarding to Fastify. In nginx, this is default behavior for the location URI; verify with
curl --path-as-isagainst staging. Deploy within 30 days as a defense-in-depth belt-and-suspenders even after patching. - Enforce authn/authz at the upstream, not at the proxy prefix — Architectural fix — if
/adminrequires being authenticated at the upstream, no prefix bypass matters. TreatrewritePrefixas *routing*, never as *authorization*. Do this alongside the patch, tracked as a hardening item within 30 days. - Add SIEM alert for upstream requests outside the expected prefix scope — The upstream sees the proxy's identity as the client. Alert on any request whose path does not begin with the intended
rewritePrefixtarget — this catches both this CVE and any future prefix-bypass regression. Deploy within 30 days. - SBOM sweep for
fastify-http-proxy(pre-scope) and@fastify/http-proxy(post-scope) — The package was renamed during the npm-scoped migration; both names exist in older lockfiles. Usenpm ls @fastify/http-proxy fastify-http-proxyor your SCA (Snyk, Dependabot, Renovate) across all 10k hosts within 30 days.
- Blocking
%in URLs at the edge — breaks legitimate percent-encoded query strings, form submissions, and non-ASCII paths. Overbroad and will cause user-visible outages. - Fastify's own route-level
onRequesthook to normalize paths — the bug is in the proxy plugin's rewrite step, not in Fastify core. A hook that mutatesrequest.urlbefore the proxy handler can help, but it's fiddly and unsupported. Just patch. - Rate limiting — irrelevant. A single request achieves impact.
- Network segmentation between proxy and upstream — does not help; the attacker is riding the proxy's authorized channel to the upstream.
- Rotating credentials / secrets — no credential compromise is involved.
- mTLS between proxy and upstream — does NOT help by itself; the proxy still authenticates to the upstream and forwards the malicious path.
Crowdsourced verification payload.
Run on any host or CI runner with Node/npm project checkouts. From the project root, invoke ./check-fastify-http-proxy.sh . (or a path to a directory to scan recursively for package-lock.json / pnpm-lock.yaml / yarn.lock). No elevated privileges required — just read access to the lockfiles.
#!/usr/bin/env bash
# noisgate verification — CVE-2026-16117 @fastify/http-proxy prefix escape
# Usage: ./check-fastify-http-proxy.sh <path-to-scan>
# Exit codes: 0 PATCHED, 1 VULNERABLE, 2 UNKNOWN
set -u
ROOT="${1:-.}"
FIXED_MAJOR=11
FIXED_MINOR=6
FIXED_PATCH=0
STATUS="UNKNOWN"
FOUND=0
ver_ge() {
# returns 0 if $1 >= FIXED
local v="$1"
local maj min pat
IFS='.' read -r maj min pat <<< "$v"
maj=${maj:-0}; min=${min:-0}; pat=${pat%%[!0-9]*}; pat=${pat:-0}
if (( maj > FIXED_MAJOR )); then return 0
elif (( maj < FIXED_MAJOR )); then return 1
elif (( min > FIXED_MINOR )); then return 0
elif (( min < FIXED_MINOR )); then return 1
elif (( pat >= FIXED_PATCH )); then return 0
else return 1
fi
}
scan_lockfile() {
local f="$1"
# Match both scoped (@fastify/http-proxy) and legacy (fastify-http-proxy)
local versions
versions=$(grep -Eo '"(@fastify/http-proxy|fastify-http-proxy)"[^{]*"version"[[:space:]]*:[[:space:]]*"[0-9]+\.[0-9]+\.[0-9]+"' "$f" 2>/dev/null \
| grep -Eo '"version"[[:space:]]*:[[:space:]]*"[0-9]+\.[0-9]+\.[0-9]+"' \
| grep -Eo '[0-9]+\.[0-9]+\.[0-9]+')
# yarn.lock style
local yv
yv=$(grep -E '^("?@?fastify[/-]http-proxy)' -A2 "$f" 2>/dev/null | grep -Eo 'version "[0-9]+\.[0-9]+\.[0-9]+"' | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+')
versions="$versions"$'\n'"$yv"
while IFS= read -r v; do
[ -z "$v" ] && continue
FOUND=1
if ver_ge "$v"; then
echo "[OK] $f -> @fastify/http-proxy $v (>= 11.6.0)"
else
echo "[VULN] $f -> @fastify/http-proxy $v (< 11.6.0 — CVE-2026-16117)"
STATUS="VULNERABLE"
fi
done <<< "$versions"
}
while IFS= read -r -d '' lock; do
scan_lockfile "$lock"
done < <(find "$ROOT" -type f \( -name package-lock.json -o -name pnpm-lock.yaml -o -name yarn.lock -o -name npm-shrinkwrap.json \) -print0 2>/dev/null)
if [ "$STATUS" = "VULNERABLE" ]; then
echo "RESULT: VULNERABLE"; exit 1
fi
if [ $FOUND -eq 1 ]; then
echo "RESULT: PATCHED"; exit 0
fi
echo "RESULT: UNKNOWN (no @fastify/http-proxy references found under $ROOT)"
exit 2
If you remember one thing.
@fastify/http-proxy and legacy fastify-http-proxy. Any hit below 11.6.0 goes on the fast track. Per the noisgate mitigation SLA for HIGH, you have 30 days to deploy compensating controls — front the proxy with a path-normalizing edge (nginx/Cloudflare) and add a SIEM alert on upstream requests outside the expected prefix; per the noisgate remediation SLA for HIGH, the actual upgrade to 11.6.0 must land within 180 days. Escalate any specific instance to CRITICAL timelines (3-day mitigation, 90-day remediation) if the proxy is internet-exposed AND fronts an upstream that relies on the prefix for access control — that combination is the only shape of this bug that reaches domain/tenant blast radius, and it deserves same-week attention.Sources
- GitHub Security Advisory GHSA-mx7v-qhg9-2mvv
- fastify/fastify-http-proxy repository
- Prior related advisory GHSA-c4qr-gmr9-v23w (CVE-2021-21322)
- OpenJS Foundation CNA advisories
- Snyk — fastify-http-proxy package
- OpenCVE — Fastify vendor listing
- CISA Vulnerability Summary (week of 2026-06-29)
- NorthIT prior-art writeup on fastify-http-proxy prefix escape
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.