A fire alarm that silently disconnects itself when you wire it with a metric-to-imperial adapter
CVE-2026-90711 affects proxy-addr versions 1.1.0 through 2.0.7, the module that powers req.ip and req.ips in Express.js. When a developer configures a trust subnet using IPv4-mapped IPv6 notation with an IPv4-sized prefix — e.g., ::ffff:10.0.0.0/8 instead of the correct ::ffff:10.0.0.0/104 — the library silently accepts the input and proceeds to trust every IPv4 address on the internet. An unauthenticated remote attacker can then inject arbitrary X-Forwarded-For headers, controlling the IP address the application reads. This defeats IP-based access control, rate limiting, geolocation enforcement, and audit-log integrity. The fix in 2.0.8 validates prefix lengths for mapped subnets.
The vendor rates this CRITICAL at 9.1, and the CVSS vector (AV:N/AC:L/PR:N/UI:N) is technically accurate *if* the misconfiguration is present. But that’s a big “if.” The overwhelming majority of Express apps configure trust proxy with plain IPv4 notation (10.0.0.0/8), a numeric hop count (app.set('trust proxy', 1)), or named presets (loopback, linklocal, uniquelocal). The IPv4-mapped IPv6 form with a wrong-length prefix is a niche footgun. The CVSS effectively bakes in AC:L for a condition that is closer to AC:H in the real world. Additionally, the impact ceiling is IP spoofing — serious for security controls, but not remote code execution. A downgrade to HIGH at 6.5 is warranted.
3 steps from start to impact.
Identify Express app with vulnerable proxy-addr
::ffff:10.0.0.0/8.- Target runs Express or uses proxy-addr directly
- Trust proxy configured with IPv4-mapped IPv6 and wrong prefix length
- Application is network-reachable
- Most Express apps use plain IPv4 notation, numeric hop count, or named presets
- The IPv6-mapped form with /8 instead of /104 is a rare configuration pattern
- No banner or error reveals whether the misconfiguration is present
Craft spoofed X-Forwarded-For header
X-Forwarded-For: <arbitrary-ip> header. The library walks the header chain, finds the attacker’s socket address in the trusted set, and returns the forged IP as req.ip. No authentication, credentials, or special tooling is required — curl -H 'X-Forwarded-For: 1.2.3.4' is sufficient.- Step 1 conditions met
- Attacker can send HTTP requests to the application
- A properly positioned WAF or reverse proxy that overwrites X-Forwarded-For before it reaches the app would strip the spoofed header
- Applications that don’t use req.ip for security decisions are unaffected even if technically vulnerable
Bypass IP-based security controls
req.ip now attacker-controlled, the spoofed address bypasses IP allowlists, rate limiters (e.g., express-rate-limit), geo-restriction middleware, and IP-based audit logging. The attacker can brute-force authentication endpoints past rate limits, access admin panels behind IP allowlists, or poison forensic trails. The blast radius is scoped to the individual application instance — not lateral to other hosts.- Application relies on req.ip for security-relevant decisions
- Rate limiting, IP allowlisting, or geo-fencing is in use
- Applications using session-based or token-based auth are not directly compromised by IP spoofing alone
- MFA on sensitive endpoints limits the value of rate-limit bypass
- Impact is per-application, not fleet-wide
The supporting signals.
| In-the-Wild Exploitation | No known exploitation. Not listed on CISA KEV. No campaigns or threat actor usage reported as of 2026-09-15. |
|---|---|
| Proof-of-Concept | Trivial to reproduce but no public PoC repo found. Exploitation requires only curl -H 'X-Forwarded-For: <spoofed>' against a misconfigured target. The barrier is identifying the misconfiguration, not crafting the exploit. |
| EPSS Score | Not yet scored (disclosed 2026-09-15, same day). Expect moderate EPSS given network vector but conditional trigger. |
| KEV Status | Not listed. No CISA KEV entry as of 2026-09-15. |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N — 9.1 CRITICAL. Network-accessible, no privileges, no interaction. C:H and I:H reflect IP-spoof-driven control bypass. A:N because no denial of service. |
| Affected Versions | proxy-addr 1.1.0 through 2.0.7. Express 4.x and 5.x bundle proxy-addr as a dependency. |
| Fixed Version | proxy-addr 2.0.8 (released 2026-09-15). Node.js June 2026 security releases also backport the fix. |
| Installed Base / Exposure | proxy-addr has ~50–80 million weekly npm downloads (Snyk, Socket). It is a transitive dependency of virtually every Express.js application. However, only apps using IPv4-mapped IPv6 trust notation with wrong prefix are exploitable. |
| Disclosure Date | 2026-09-15 (coordinated disclosure, same-day patch). |
| Reporters | kagebunsher and kustundag (reporters); UlisesGascon (developer/maintainer). |
noisgate verdict.
The single most decisive factor driving the downgrade from CRITICAL to HIGH is the narrow trigger condition: exploitation requires the target application to have configured trust proxy using IPv4-mapped IPv6 notation with an incorrect IPv4-sized prefix, a pattern used by a small minority of the massive Express installed base. The impact ceiling of IP spoofing (not RCE) further compresses the real-world severity below the vendor’s 9.1 score.
Why this verdict
- Conditional misconfiguration narrows the population dramatically. The bug only fires when trust proxy is set using
::ffff:IPv4-mapped notation with an IPv4-sized prefix. The vast majority of Express apps useapp.set('trust proxy', 1), plain IPv4 CIDR, or named presets likeloopback. This is closer to AC:H than AC:L. - Impact ceiling is IP spoofing, not code execution. The attacker controls
req.ip, which can bypass rate limiting, IP allowlists, and audit logging. This is serious but does not grant shell access, data exfiltration of arbitrary records, or lateral movement. C:H and I:H in the vector overstate the real-world blast radius. - Massive install base keeps this from dropping to MEDIUM. proxy-addr has 50–80M weekly downloads. Even if only 0.1–1% of apps use the vulnerable configuration, that’s tens of thousands of potentially affected services. The unauthenticated, zero-interaction attack vector means any exposed misconfigured app is immediately exploitable.
- Role multiplier: proxy-addr is a library used across all application tiers, not a canonically high-value infrastructure component. In typical roles (web APIs, SPAs, microservices), IP spoofing’s blast radius is limited to the individual application (rate-limit bypass, log poisoning). In higher-value roles (API gateways enforcing IP allowlists for admin panels, authentication services using IP for risk scoring), the impact escalates to auth-bypass-adjacent outcomes but remains scoped to that service, not fleet-wide. No realistic chain leads to domain takeover, fleet compromise, or supply-chain pivot from this bug alone. The floor is HIGH, not CRITICAL.
Why not higher?
Promoting to CRITICAL would require either active exploitation (none observed), a universally triggered condition (the IPv6-mapped misconfiguration is niche), or an impact that reaches RCE, domain takeover, or fleet-scale compromise (IP spoofing alone does not). The vendor’s 9.1 assumes the worst-case configuration is the common case; real-world deployment data does not support that assumption.
Why not lower?
Dropping to MEDIUM would undercount the risk posed by the enormous installed base and the zero-auth, zero-interaction network vector. The silent failure mode (no error on misconfiguration) means affected apps have no indication they’re exposed. Any app that IS misconfigured is trivially exploitable, and the IP spoofing impact against rate limiters and allowlists is operationally significant.
What to do — in priority order.
- Audit trust proxy configuration across all Express apps — Search codebases for
::ffff:in trust proxy settings and for direct proxy-addrcompile()calls using IPv6-mapped notation. Convert any such entries to plain IPv4 CIDR (e.g.,10.0.0.0/8) or use prefix lengths >= 97 for mapped addresses. This eliminates the vulnerability without upgrading. Target completion within the noisgate mitigation SLA of 30 days for HIGH. - Upgrade proxy-addr to 2.0.8 — Run
npm audit fixor explicitly installproxy-addr@^2.0.8. For Express apps, ensure the resolved proxy-addr version inpackage-lock.jsonis >= 2.0.8. Deploy within the noisgate remediation SLA of 180 days for HIGH. - Ensure reverse proxy overwrites X-Forwarded-For — Configure your edge reverse proxy (nginx, HAProxy, Envoy, AWS ALB) to set (not append) the
X-Forwarded-Forheader. This prevents clients from injecting arbitrary values regardless of proxy-addr’s trust logic. This is a defense-in-depth measure that should already be in place. - Add req.ip anomaly detection — Log both the socket remote address and
req.ipin application access logs. Alert on cases wherereq.ipis an internal/RFC1918 address but the socket address is external. This detects active exploitation attempts.
- WAF IP reputation rules — the spoofed IP in X-Forwarded-For is chosen by the attacker and can be any “clean” address, so reputation-based blocking won’t flag it.
- Network-level ACLs on the application port — the attacker connects from their real IP (which may be allowed); the spoofing happens at the HTTP header layer, not the TCP layer.
- Upgrading Express without checking proxy-addr — Express 4.x may pin an older proxy-addr. You must verify the resolved transitive dependency version, not just the Express version.
Crowdsourced verification payload.
Run this script on each application host or in your CI pipeline against the project’s node_modules. It checks the installed proxy-addr version and optionally scans for IPv6-mapped trust configurations. Requires read access to the project directory. Example: bash check_proxyaddr.sh /app
#!/usr/bin/env bash
# check_proxyaddr.sh - Check for CVE-2026-90711 (proxy-addr < 2.0.8)
# Usage: bash check_proxyaddr.sh <project_root>
# Exit codes: 1=VULNERABLE, 0=PATCHED, 2=UNKNOWN
set -euo pipefail
PROJECT_DIR="${1:-.}"
VULN_VERSION_CEILING="2.0.8"
RESULT="UNKNOWN"
# Find proxy-addr package.json
PKG_JSON=$(find "$PROJECT_DIR" -path '*/proxy-addr/package.json' -not -path '*/node_modules/.cache/*' 2>/dev/null | head -1)
if [ -z "$PKG_JSON" ]; then
echo "[UNKNOWN] proxy-addr not found in $PROJECT_DIR"
exit 2
fi
# Extract version
VERSION=$(grep -o '"version": *"[^"]*"' "$PKG_JSON" | head -1 | grep -o '[0-9][0-9.]*')
if [ -z "$VERSION" ]; then
echo "[UNKNOWN] Could not parse proxy-addr version from $PKG_JSON"
exit 2
fi
echo "Found proxy-addr version: $VERSION at $PKG_JSON"
# Compare versions using sort -V
MIN_SAFE=$(printf '%s\n%s' "$VERSION" "$VULN_VERSION_CEILING" | sort -V | head -1)
if [ "$VERSION" = "$VULN_VERSION_CEILING" ] || [ "$MIN_SAFE" = "$VULN_VERSION_CEILING" ]; then
RESULT="PATCHED"
else
# Check if version >= 1.1.0 (affected range start)
MIN_AFFECTED=$(printf '%s\n%s' "$VERSION" "1.1.0" | sort -V | head -1)
if [ "$MIN_AFFECTED" = "1.1.0" ] || [ "$VERSION" = "1.1.0" ]; then
RESULT="VULNERABLE"
else
RESULT="PATCHED"
fi
fi
# Bonus: scan for IPv6-mapped trust config (the actual trigger)
echo ""
echo "--- Scanning for IPv6-mapped trust configurations ---"
MAPPED_HITS=$(grep -rn '::ffff:' "$PROJECT_DIR" --include='*.js' --include='*.ts' --include='*.json' 2>/dev/null | grep -iv node_modules/proxy-addr || true)
if [ -n "$MAPPED_HITS" ]; then
echo "[WARNING] Found IPv4-mapped IPv6 references in application code:"
echo "$MAPPED_HITS"
else
echo "[OK] No IPv4-mapped IPv6 trust configurations found in app code."
fi
echo ""
echo "[$RESULT] proxy-addr $VERSION"
[ "$RESULT" = "VULNERABLE" ] && exit 1
[ "$RESULT" = "PATCHED" ] && exit 0
exit 2If you remember one thing.
::ffff: in trust proxy configurations — if you find none, your exposure to this CVE is effectively zero and you can treat the upgrade as routine hygiene within the noisgate remediation SLA of 180 days. If you DO find IPv6-mapped trust configs with IPv4-sized prefixes, rewrite them to plain IPv4 CIDR notation immediately (this is your compensating control, due within the noisgate mitigation SLA of 30 days). In parallel, upgrade proxy-addr to 2.0.8 across all projects via npm audit fix and lock the resolved version. Verify your edge reverse proxy *sets* (not appends) X-Forwarded-For as a defense-in-depth layer. There is no active exploitation or KEV listing, so this is a measured response, not a fire drill — but the silent-failure nature of the bug means you should confirm your configs this week rather than assuming you’re safe.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.