Like a bouncer who checks IDs at the door but waves everyone through the side entrance
CVE-2026-16732 is a header-spoofing vulnerability in Fastify (Node.js web framework), affecting versions 5.8.3 through 5.12.0. The flaw lives in the trustProxy: <number> configuration — the canonical documented way to tell Fastify "I'm behind N reverse proxies." An earlier fix for CVE-2026-3635 added a guard that calls proxyFn(socket.remoteAddress, 0) before trusting X-Forwarded-* headers. Problem: when trustProxy is a number, the compiled predicate *structurally ignores the address argument*, collapsing to 0 < tp — always true for any tp >= 1. An attacker who can reach the Fastify origin directly (bypassing the front-facing proxy) can forge X-Forwarded-Host, X-Forwarded-Proto, and X-Forwarded-For, leading to host injection in URLs, HTTPS-enforcement bypass, secure-cookie and CSRF-origin bypass, and cache poisoning.
The vendor's MEDIUM / 6.1 rating is directionally correct but slightly generous. The CVSS vector AV:A/AC:H already accounts for the hardest prerequisite — the attacker must reach the Fastify process directly, not through its proxy — and the S:C/C:H captures the downstream impact. However, the vendor rated I:N (no integrity impact) despite listing cache poisoning and CSRF-origin bypass as impacts, both of which are textbook integrity violations. That said, the *indirect* nature of the attack (spoofing enables further exploitation, it is not the exploitation itself) and the strong prerequisite of proxy bypass keep this firmly in MEDIUM territory. We adjust the score down to 5.5 because the real-world exploitation chain requires a second vulnerability or misconfiguration to convert header spoofing into actual data compromise.
4 steps from start to impact.
Direct access to Fastify origin
0.0.0.0 on a high port that firewall rules should block.- Network path to the Fastify listening port, bypassing the proxy
- Well-architected deployments bind Fastify to localhost or a private subnet, making direct access impossible without prior internal compromise
- Cloud-native deployments (K8s, ECS) abstract the origin behind a Service; no direct pod routing from external networks
- Even in VM deployments, host firewalls (iptables, security groups) should block the app port from non-proxy sources
Confirm trustProxy numeric config
trustProxy: <number> (e.g., trustProxy: 1). This is the documented canonical form for 'behind one reverse proxy.' The attacker can probe by sending a request with a crafted X-Forwarded-Host header and observing whether the response reflects the spoofed host (e.g., in Location headers, generated URLs, or error pages).- Fastify configured with
trustProxy: <number>(versions 5.8.3–5.12.0)
- Applications using
trustProxy: true, a CIDR range, or a custom function are NOT affected - After CVE-2026-3635, some teams already migrated to CIDR/function forms
- Fastify 5.12.1 removes the numeric form entirely, so patched apps are immune
trustProxy: followed by a numeric literal. npm audit flags the vulnerable version range.Forge X-Forwarded-* headers
X-Forwarded-Host: evil.com, X-Forwarded-Proto: http, X-Forwarded-For: <arbitrary IP>. Fastify's request.host, request.hostname, request.protocol, request.ip, and request.ips all return the attacker-controlled values. No special tooling needed — curl -H suffices.- Steps 1 and 2 satisfied
- The spoofing alone does not exfiltrate data or execute code; it manipulates Fastify's view of the request metadata
Exploit spoofed context
X-Forwarded-Proto: http to strip secure-cookie flags, CSRF-origin bypass by aligning the spoofed host with the attacker's origin, or cache poisoning if a CDN/cache layer keys on Host. Each of these requires the application to use the spoofed field in a security-relevant decision.- Application uses
request.host,request.protocol, orrequest.ipfor security logic (URL generation, cookie flags, rate limiting, origin validation)
- Applications that hardcode their canonical URL or use an environment variable for the base URL are not affected by host injection
- Cache poisoning requires a caching layer that trusts and keys on the Host header from the origin response — not all architectures have this
- CSRF bypass requires the app to rely on Origin/Host header matching rather than CSRF tokens
trustProxy: 1 to trustProxy: '10.0.0.0/8' (your proxy subnet) or a custom validation function that checks socket.remoteAddress. This completely eliminates the vulnerable code path without upgrading Fastify. This is a one-line config change — deploy within the 365-day noisgate remediation SLA for MEDIUM, or sooner if your app handles sensitive auth flows.trustProxy form at runtime and removes it from TypeScript types, forcing a migration to CIDR/function forms. Standard npm update — npm install fastify@latest. Apply within the 365-day noisgate remediation SLA for MEDIUM.request.hostname against an allowlist of expected domains, rejecting or rewriting mismatches. This mitigates the host-injection and cache-poisoning attack vectors even if the spoofing succeeds. Does not fix the root cause but limits blast radius.- Upgrading only the reverse proxy (nginx, ALB, Caddy) — the bug is in Fastify's predicate logic, not in the proxy. The proxy correctly sets headers; Fastify incorrectly trusts them from non-proxy sources.
- **WAF rules blocking X-Forwarded-* headers** — these headers are legitimate and required for normal operation; stripping them at the WAF breaks the application. You'd need conditional stripping (only from non-proxy sources), which is effectively the same as fixing the network path.
- Rate limiting — this is a spoofing/logic bug, not a volumetric or brute-force attack. Rate limits do not address the vulnerability.
The supporting signals.
| In-the-Wild Exploitation | No evidence. Not listed in CISA KEV. No campaigns attributed. GreyNoise shows no scanning activity targeting this specific CVE. |
|---|---|
| Proof-of-Concept | None public. No PoC repos found on GitHub named after CVE-2026-16732. Not indexed on pocindex.io. Exploitation is trivial with curl -H once prerequisites are met, but no weaponized tooling exists. |
| EPSS | 0.00158 (bottom ~15th percentile) — exploitation probability is very low over the next 30 days. |
| KEV Status | Not listed. No CISA KEV entry as of 2026-10-08. |
| CVSS Vector | CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:C/C:H/I:N/A:N — Adjacent network (must bypass proxy), high complexity, no privileges, changed scope, high confidentiality impact. Note: vendor rates I:N despite cache poisoning and CSRF bypass being integrity impacts. |
| Affected Versions | fastify >= 5.8.3, < 5.12.1 (versions 5.8.3, 5.8.4, 5.8.5, 5.9.0, 5.10.0, 5.11.0–5.11.3, 5.12.0). Only the trustProxy: <number> config path is vulnerable. |
| Fixed Version | fastify 5.12.1 — the numeric form of trustProxy is disabled at runtime and removed from the TypeScript type union. No distro backports (npm ecosystem). |
| Scanning / Exposure | Fastify has ~5–12M weekly npm downloads. However, the vulnerable code path requires the specific numeric trustProxy config AND direct origin access. Shodan/Censys exposure of raw Node.js ports is generally low in production environments. |
| Disclosure Date | 2026-08-18 (disclosed), advisory published 2026-09-02. Patch (v5.12.1) released concurrently with advisory. |
| Reporter / Credits | Reported by alimony (GitHub). Fix developed by mcollina (Matteo Collina, Fastify lead maintainer). Reviewed by climba03003 and UlisesGascon. |
Sources.
Why this verdict
- Proxy-bypass prerequisite is real friction. The CVSS vector encodes AV:A (Adjacent), meaning the attacker must reach the Fastify process directly, not through its reverse proxy. In cloud-native deployments (Kubernetes, ECS, Lambda), the origin is behind a Service/ALB with no direct external path. This prerequisite implies the attacker is already inside the network — post-initial-access — which compounds the difficulty.
- Spoofing is an enabler, not the exploit itself. Forging X-Forwarded-* headers gives the attacker control over
request.host,request.ip, andrequest.protocol, but converting that into actual impact (credential theft, cache poisoning, CSRF bypass) requires the application to make security decisions based on those fields AND a second exploitable condition (e.g., cacheable responses keyed on Host, Origin-based CSRF checks without tokens). The chain has two links, not one. - Configuration scope narrows the population. Only apps using
trustProxy: <number>are affected. Apps usingtrustProxy: true, CIDR notation, or a custom function are immune. While the numeric form is the canonical documented example, security-conscious teams following CVE-2026-3635 guidance may have already migrated. - Role multiplier: Fastify is a general-purpose Node.js web framework. Its canonical deployment role is *application-tier API server or microservice backend* (role b). It is NOT an identity provider, certificate authority, hypervisor, backup system, kernel agent, or network edge appliance. In rare cases, a Fastify app might serve as an auth microservice, but this is not the canonical role (<<10% of installs). The blast radius of successful exploitation is host-level to tenant-level (spoofed host in password-reset emails, session hijack via cookie downgrade), not domain-wide or fleet-wide. No high-value-role floor override applies.
- No exploitation evidence or tooling. EPSS is 0.00158, no KEV listing, no public PoC, no scanning activity on GreyNoise. The vulnerability is not attracting attacker attention.
Why not higher?
The vendor's CVSS vector includes S:C/C:H, which could suggest HIGH severity if the proxy-bypass prerequisite were easier to satisfy. However, in production architectures, Fastify origins are not directly reachable from external networks — this is not an assumption but a standard deployment pattern enforced by cloud infrastructure (security groups, K8s NetworkPolicies, ALBs). Promoting to HIGH would require evidence that a significant fraction of Fastify deployments expose the origin port directly, and no such evidence exists. The lack of any PoC, exploitation, or KEV listing further argues against elevation.
Why not lower?
Despite the friction, the trustProxy: <number> config is the *documented canonical form* — not an obscure edge case. Any team that followed the Fastify docs and wrote trustProxy: 1 is affected. The downstream impacts (host injection in password-reset URLs, HTTPS downgrade stripping secure cookies) are real security consequences when the chain completes. Dropping to LOW would underweight the confidentiality impact that the vendor correctly identified as HIGH when the preconditions are met.
Crowdsourced verification payload.
Run this on each host (or in CI) where a Node.js application using Fastify is deployed. Execute as the application user or any user with read access to node_modules. Example: bash check_cve_2026_16732.sh /opt/myapp
#!/usr/bin/env bash
# check_cve_2026_16732.sh — Detect CVE-2026-16732 (Fastify trustProxy hop-count spoofing)
# Usage: bash check_cve_2026_16732.sh <app_directory>
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN
set -euo pipefail
APP_DIR="${1:-.}"
PKG_JSON="${APP_DIR}/node_modules/fastify/package.json"
if [ ! -f "$PKG_JSON" ]; then
echo "UNKNOWN — fastify not found in ${APP_DIR}/node_modules"
exit 2
fi
VERSION=$(grep '"version"' "$PKG_JSON" | head -1 | sed 's/.*"version": *"\([^"]*\)".*/\1/')
if [ -z "$VERSION" ]; then
echo "UNKNOWN — could not parse fastify version from $PKG_JSON"
exit 2
fi
# Affected: >= 5.8.3, < 5.12.1
# We compare using sort -V
MIN_AFFECTED="5.8.3"
FIXED="5.12.1"
is_gte() { [ "$(printf '%s\n%s' "$1" "$2" | sort -V | head -1)" = "$2" ]; }
is_lt() { [ "$(printf '%s\n%s' "$1" "$2" | sort -V | head -1)" = "$1" ] && [ "$1" != "$2" ]; }
if is_gte "$VERSION" "$MIN_AFFECTED" && is_lt "$VERSION" "$FIXED"; then
echo "VULNERABLE — fastify $VERSION is in the affected range (>= $MIN_AFFECTED, < $FIXED)"
# Bonus: check if trustProxy numeric form is used in app code
if grep -rq 'trustProxy.*[0-9]' "${APP_DIR}/"*.js "${APP_DIR}/src/" "${APP_DIR}/config/" 2>/dev/null; then
echo " WARNING: trustProxy numeric config pattern detected in application source"
fi
exit 1
elif is_gte "$VERSION" "$FIXED"; then
echo "PATCHED — fastify $VERSION is >= $FIXED"
exit 0
else
echo "PATCHED — fastify $VERSION is below the affected range (< $MIN_AFFECTED)"
exit 0
fi