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
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. |
noisgate verdict.
The single most decisive factor is the narrow configuration prerequisite: the vulnerability only fires when trust-proxy is set using IPv4-mapped IPv6 CIDR with a prefix shorter than /96, an uncommon pattern that excludes the vast majority of Express and Fastify deployments using standard configurations (trust proxy: true, numeric hop counts, plain IPv4 CIDR, or named presets). Without this specific misconfiguration, the vulnerability has zero impact regardless of proxy-addr version.
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.
What to do — in priority order.
- Audit trust-proxy configuration for IPv4-mapped IPv6 notation — Search your codebase for
::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. - Upgrade proxy-addr to ≥ 2.0.8 — Run
npm audit fixor pinproxy-addr@^2.0.8in your lockfile. The fix adds prefix validation and early canonicalization. This is the definitive remediation — target within the noisgate 365-day remediation SLA. - Layer authentication above IP-based controls — Ensure no security-critical decision (admin access, API authorization) relies solely on
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. - Deploy WAF rules to detect X-Forwarded-For injection — Configure your WAF or reverse proxy to strip or normalize
X-Forwarded-Forheaders 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.
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
fiIf you remember one thing.
grep -r '::ffff:' src/ across your Node.js repos — if you find any matches in trust-proxy configuration, rewrite them to plain IPv4 CIDR immediately (this is a zero-risk config change). Then schedule the proxy-addr upgrade to ≥ 2.0.8 via npm audit fix within the noisgate remediation SLA of 365 days. No noisgate mitigation SLA applies at MEDIUM severity — go straight to the remediation window. If you *do* find the vulnerable config pattern, treat it as an urgent config fix (hours, not days) since your app is actively fail-open to IP spoofing. For everyone else, this is routine dependency hygiene — update when convenient within the year.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.