It's like a VIP door that accidentally accepts every ID, but only if the bouncer was told to read passports upside-down
CVE-2026-92395 (also tracked as CVE-2026-90711 / GHSA-jqcg-44mw-7w3h) affects @fastify/proxy-addr and the upstream proxy-addr module versions 1.1.0 through 2.0.7, which underpin req.ip and req.ips in both Express and Fastify. When a developer configures a trust-proxy subnet using IPv4-mapped IPv6 notation with an IPv4-length prefix — e.g. ::ffff:10.0.0.0/8 instead of the correct ::ffff:10.0.0.0/104 — the library silently compiles the subnet to an all-zero bitmask that matches every IPv4 address. Any unauthenticated remote client is then treated as a trusted proxy at hop 0, meaning attacker-controlled X-Forwarded-For values flow straight into IP-based ACLs, rate limiters, geolocation, and audit logs.
The vendor rates this CRITICAL at 9.1 (AV:N/AC:L/PR:N/UI:N), which is the correct score *if your app actually uses the broken notation*. But the CVSS vector encodes zero preconditions beyond network reachability, hiding the single biggest real-world constraint: the vulnerability only fires when trust-proxy is configured with an IPv4-mapped IPv6 CIDR using a prefix shorter than /96. The overwhelmingly common patterns — trust proxy: 1, 'loopback', 'uniquelocal', or plain IPv4 CIDR like '10.0.0.0/8' — are completely unaffected. This turns a theoretical 9.1 into a much narrower exposure window, making the vendor severity misleading for most defenders.
4 steps from start to impact.
Identify a Fastify/Express app using proxy-addr ≤ 2.0.7
proxy-addr module is a transitive dependency of both frameworks and is used whenever trust proxy is configured. The attacker does not need to know the exact version at this stage — the next step tests exploitability directly.- Target runs Express or Fastify with proxy-addr ≤ 2.0.7
- Target is network-reachable (HTTP/HTTPS)
- proxy-addr version is not externally fingerprint-able; attacker must probe blind
- ~50M weekly npm downloads, but only a fraction use IPv4-mapped IPv6 trust config
Confirm IPv4-mapped IPv6 trust subnet misconfiguration
X-Forwarded-For: 1.2.3.4 header and observes the application's behavior — for instance, checking whether rate limits are bypassed, whether a geo-locked endpoint serves content, or whether admin IP whitelists no longer block access. If the trust config uses the broken IPv4-mapped IPv6 notation, the spoofed IP is accepted verbatim.- Application configures trust proxy with
::ffff:x.x.x.x/<prefix<96> - Application uses
req.ipfor a security-relevant decision
- This is the critical gate: the vast majority of Express/Fastify apps use
trust proxy: true, a number,'loopback', or plain IPv4 CIDR — none of which are affected - No tooling exists to detect this misconfiguration remotely; it requires source-code review or behavioral probing
::ffff: in trust proxy config would catch this.Spoof source IP via X-Forwarded-For
X-Forwarded-For to any desired IP address. The application's req.ip now returns the attacker's chosen value. This defeats IP-based access control lists, rate limiting, geolocation enforcement, and corrupts audit logs with false source addresses.- Steps 1 and 2 succeeded
- Impact is bounded by what the application does with
req.ip— many apps use it only for logging, not hard security decisions - Modern auth is token-based (JWT, session cookies), not IP-based — IP ACLs are a defense-in-depth layer, not the primary gate
Exploit downstream trust decisions
req.ip, the attacker can bypass IP whitelists protecting admin panels, evade rate limiting to enable credential stuffing, bypass geo-fencing for content or compliance boundaries, or poison audit trails to hide their origin during a broader attack chain.- Application relies on IP address for access control, rate limiting, or geolocation
- Most production environments layer authentication (OAuth, SAML, MFA) above IP-based controls
- IP spoofing alone does not grant authenticated access — it removes one layer of defense-in-depth
::ffff: in trust-proxy settings. If found with a prefix < /96, rewrite to plain IPv4 CIDR (e.g., 10.0.0.0/8) or the correct mapped form (::ffff:10.0.0.0/104). This eliminates the vulnerability regardless of proxy-addr version. No mitigation SLA applies at MEDIUM — go straight to the 365-day remediation window.npm audit fix or pin proxy-addr@^2.0.8 in your lockfile. The fix adds prefix validation and early canonicalization. This is the definitive remediation — target within the noisgate 365-day remediation SLA.req.ip. Use token-based auth (JWT, OAuth, session cookies) with MFA as the primary gate. IP checks should be a supplementary signal, not the sole gatekeeper.X-Forwarded-For headers from external clients before they reach your application. Nginx: proxy_set_header X-Forwarded-For $remote_addr; overwrites rather than appends.- Network-level IP blocking — the attack uses legitimate HTTP requests with spoofed header values, not spoofed IP packets. Firewall rules and network ACLs cannot detect or prevent X-Forwarded-For manipulation.
- Upgrading Express or Fastify alone — the vulnerability is in the transitive
proxy-addrdependency. You must verify that the lockfile resolves to proxy-addr ≥ 2.0.8 specifically, as framework upgrades may not bump this transitive dep. - Rate limiting at the application layer — if rate limiting itself depends on
req.ip(which uses the vulnerable proxy-addr), the rate limiter is also bypassed. Rate limits must be enforced upstream at the reverse proxy or CDN layer.
The supporting signals.
| In-the-wild exploitation | None observed. Not listed in CISA KEV. No campaigns or threat actor usage reported as of 2026-09-16. |
|---|---|
| Proof-of-concept | No public PoC. Exploitation is trivial (craft an X-Forwarded-For header) but requires confirming the specific trust-proxy misconfiguration on the target. No named exploit tools. |
| EPSS | 0.19% (8.5th percentile) — very low exploit probability, consistent with a configuration-dependent trigger. |
| KEV status | Not listed. No deadline from CISA BOD 22-01. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N — Network-reachable, no auth, no interaction. C:H and I:H reflect full control over the IP value the app trusts. The vector does not encode the configuration prerequisite, which is the primary real-world constraint. |
| Affected versions | proxy-addr 1.1.0 – 2.0.7 and @fastify/proxy-addr equivalents. The upstream jshttp/proxy-addr and the Fastify fork share the same vulnerable code path. |
| Fixed versions | proxy-addr ≥ 2.0.8 and @fastify/proxy-addr ≥ 2.0.8. Express 4.x and 5.x pull proxy-addr transitively — update the lockfile dependency. |
| Scanning / exposure | Not externally detectable. No Shodan/Censys/GreyNoise signatures exist because the vulnerability is in application-layer trust logic, not a network service banner. The ~50–80M weekly npm downloads represent the total dependency population, but the exploitable subset requires the specific IPv4-mapped IPv6 trust configuration. |
| Disclosure date | 2026-09-15 (published). Advisory: GHSA-jqcg-44mw-7w3h. |
| Reporting researcher | Not publicly attributed. Fix authored by Matteo Collina (mcollina) via PR #117. |
Sources.
Why this verdict
- Configuration gate eliminates most of the population. The standard Express/Fastify trust-proxy patterns —
true,1,'loopback','uniquelocal','10.0.0.0/8'— are all immune. Only the niche pattern::ffff:x.x.x.x/<short-prefix>triggers the bug. This is not a default, not in any official tutorial, and not in the Express or Fastify starter templates. - Impact is defense-in-depth erosion, not direct compromise. IP spoofing lets an attacker bypass IP-based ACLs and rate limits, but modern applications gate authentication on tokens, sessions, and MFA — not source IP alone. The CVSS C:H/I:H overstates the standalone impact for most apps.
- EPSS confirms low real-world risk. At 0.19% (8.5th percentile), FIRST's model agrees this is unlikely to see exploitation — consistent with a vulnerability that requires a specific, uncommon configuration.
- Role multiplier: proxy-addr is a transitive dependency of Express/Fastify, which serve as API backends, BFF layers, and occasionally API gateways. In an API gateway role (high-value), a successful IP spoof could bypass IP whitelists protecting internal microservices — blast radius is potentially multi-service. However, (a) the configuration prerequisite still applies, dramatically limiting exposure, and (b) dedicated API gateways (Kong, Envoy, AWS API Gateway) don't use proxy-addr. Express/Fastify-as-gateway is a small-shop pattern, not a canonical high-value role representing ≥10% of the installed base. The floor does not elevate above MEDIUM.
- No exploitation evidence. No KEV listing, no observed campaigns, no public PoC, and the bug was disclosed only yesterday (2026-09-15). The attack surface is further limited by the fact that no external scanner can detect the vulnerable configuration.
Why not higher?
To warrant HIGH, the vulnerability would need to affect a significant fraction of the installed base without additional preconditions. Here, the IPv4-mapped IPv6 trust notation is an anti-pattern that most developers never use — the standard configurations are immune. Additionally, the impact is IP spoofing (a defense-in-depth bypass), not RCE or direct data exfiltration, and there is no evidence of exploitation or a weaponized PoC.
Why not lower?
Despite the narrow trigger, the fail-open behavior is genuinely dangerous for the small number of affected deployments: silent acceptance of a plausible-looking configuration that then trusts *every IPv4 address on the internet*. The underlying library has 50–80M weekly downloads, so even a small percentage of misconfigured apps represents a meaningful absolute number. The attack is unauthenticated and network-reachable with zero user interaction, which keeps it above LOW.
Crowdsourced verification payload.
Run this on any host with access to the application's package-lock.json or node_modules directory. No special privileges required. Example: bash check_proxy_addr.sh /path/to/your/app
#!/usr/bin/env bash
# check_proxy_addr.sh — Verify proxy-addr version for CVE-2026-92395 / CVE-2026-90711
# Usage: bash check_proxy_addr.sh /path/to/node/app
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN
set -euo pipefail
APP_DIR="${1:-.}"
FIXED_MAJOR=2
FIXED_MINOR=0
FIXED_PATCH=8
# Try package-lock.json first, then node_modules
if [ -f "$APP_DIR/package-lock.json" ]; then
VERSION=$(python3 -c "
import json, sys
with open('$APP_DIR/package-lock.json') as f:
lock = json.load(f)
# lockfile v2/v3
for key, val in lock.get('packages', {}).items():
if key.endswith('/proxy-addr') or key.endswith('/@fastify/proxy-addr'):
print(val.get('version', ''))
sys.exit(0)
# lockfile v1
for key, val in lock.get('dependencies', {}).items():
if key in ('proxy-addr', '@fastify/proxy-addr'):
print(val.get('version', ''))
sys.exit(0)
print('')
" 2>/dev/null)
elif [ -f "$APP_DIR/node_modules/proxy-addr/package.json" ]; then
VERSION=$(python3 -c "
import json
with open('$APP_DIR/node_modules/proxy-addr/package.json') as f:
print(json.load(f).get('version', ''))
" 2>/dev/null)
elif [ -f "$APP_DIR/node_modules/@fastify/proxy-addr/package.json" ]; then
VERSION=$(python3 -c "
import json
with open('$APP_DIR/node_modules/@fastify/proxy-addr/package.json') as f:
print(json.load(f).get('version', ''))
" 2>/dev/null)
else
echo "UNKNOWN — proxy-addr not found in $APP_DIR"
exit 2
fi
if [ -z "$VERSION" ]; then
echo "UNKNOWN — could not determine proxy-addr version"
exit 2
fi
# Parse semver
IFS='.' read -r MAJOR MINOR PATCH <<< "$(echo "$VERSION" | sed 's/[^0-9.]//g')"
MAJOR=${MAJOR:-0}; MINOR=${MINOR:-0}; PATCH=${PATCH:-0}
echo "proxy-addr version: $VERSION"
if [ "$MAJOR" -gt "$FIXED_MAJOR" ] || \
{ [ "$MAJOR" -eq "$FIXED_MAJOR" ] && [ "$MINOR" -gt "$FIXED_MINOR" ]; } || \
{ [ "$MAJOR" -eq "$FIXED_MAJOR" ] && [ "$MINOR" -eq "$FIXED_MINOR" ] && [ "$PATCH" -ge "$FIXED_PATCH" ]; }; then
echo "PATCHED — proxy-addr $VERSION >= 2.0.8"
exit 0
else
echo "VULNERABLE — proxy-addr $VERSION < 2.0.8 (CVE-2026-92395)"
# Bonus: check for the actual misconfiguration in source
echo ""
echo "Checking for IPv4-mapped IPv6 trust config in source files..."
HITS=$(grep -rn '::ffff:' "$APP_DIR/" --include='*.js' --include='*.ts' --include='*.mjs' --include='*.cjs' 2>/dev/null | grep -i 'trust\|proxy' || true)
if [ -n "$HITS" ]; then
echo "WARNING — Found IPv4-mapped IPv6 trust configuration:"
echo "$HITS"
echo ""
echo "VULNERABLE — version is affected AND suspicious trust config found"
else
echo "Note: No IPv4-mapped IPv6 trust config found in source (may not be exploitable)"
echo "VULNERABLE — version is affected but exploitability depends on trust config"
fi
exit 1
fi