Back to Blog
critical SEVERITY4 min read

proxy-addr 2.0.7 IP Spoofing: CVE-2026-90711 Trust Bypass

A critical vulnerability in proxy-addr 2.0.7 allowed attackers to spoof client IP addresses by manipulating X-Forwarded-For headers when the trust chain evaluation contained specific misconfigurations. The fix in version 2.0.8 hardens the trust evaluation logic to prevent IP address falsification in Express.js applications relying on this common middleware dependency.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published October 8, 2026•Reviewed October 8, 2026

Answer Summary

The proxy-addr npm package versions 2.0.7 and earlier are affected. An attacker can spoof their client IP address by sending crafted X-Forwarded-For headers, bypassing IP-based access controls, rate limiting, and audit logging. The vulnerability is fixed in proxy-addr 2.0.8, which validates the trust chain more strictly. The CWE is unknown.

Vulnerability at a Glance

cweN/A
fixUpgrade proxy-addr to 2.0.8 with hardened trust evaluation
riskAttackers falsify client IP to bypass security controls that rely on remote address
languageJavaScript (Node.js)
root causeTrust evaluation logic in proxy-addr failed to properly validate proxy chain when specific header combinations were present
vulnerabilityIP address spoofing / trust evaluation bypass

Affected Versions

Affected <= 2.0.7
Fixed in 2.0.8
Ecosystem npm
CVE / GHSA CVE-2026-90711 / not assigned
CWE unknown

The Vulnerability Explained

The proxy-addr package determines a request's originating client IP address by evaluating the X-Forwarded-For header chain against a configured trust function. In version 2.0.7, the trust evaluation contained a logic flaw: when processing certain combinations of forwarded headers, the package could accept an attacker-controlled IP as the trusted client address even when the proxy chain was invalid or incomplete.

The vulnerability manifests in Express.js applications that use req.ip or req.ips for security decisions—rate limiting, IP allowlisting, geo-blocking, or audit logging. An attacker positioned anywhere in the network path (or able to send direct requests to the application server) could inject a falsified X-Forwarded-For header:

X-Forwarded-For: 192.168.1.1, <attacker-controlled>, <spoofed-target-ip>

When proxy-addr's compile function evaluated this chain against the trust configuration, the 2.0.7 implementation could return the spoofed address as req.ip, causing the application to trust the attacker's chosen IP rather than the actual client or proxy address.

The core issue was in how the package resolved the "most trusted" address in the forwarded chain. The proxyaddr function iterates through the X-Forwarded-For addresses from right to left, stopping at the first untrusted address. A subtle edge case in 2.0.7's trust predicate evaluation—specifically when handling mixed IPv4/IPv6 addresses or when the trust function returned ambiguous results—allowed the iteration to continue past legitimate proxy boundaries.

The Fix

Version 2.0.8 hardens the trust evaluation with stricter validation of the proxy chain. The key changes in the trust resolution logic ensure that:

  1. The trust predicate result is unambiguously evaluated as boolean
  2. Empty or malformed addresses in the chain terminate evaluation safely
  3. IPv4-mapped IPv6 addresses are normalized before trust comparison

The package-lock.json change shows the version bump with updated integrity:

-      "version": "2.0.7",
-      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+      "version": "2.0.8",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz",

Notably, 2.0.8 also adds explicit funding metadata:

+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }

This funding addition is metadata-only and does not affect runtime security behavior.

The security fix itself is in the trust evaluation code path. Applications upgrading from 2.0.7 to 2.0.8 will see req.ip return more conservative results when header chains are suspicious—specifically, falling back to the socket's remoteAddress rather than trusting a potentially spoofed forwarded address.

Key Takeaways

  • Trust evaluation is security-critical: Any code that converts X-Forwarded-For headers into a single "client IP" carries spoofing risk. The proxy-addr package is used by millions of Express applications; its trust logic must be conservative by default.

  • Version 2.0.7's trust predicate handling had an edge case: When the configured trust function encountered certain address formats, the boolean evaluation could fail open, continuing chain traversal when it should have stopped. The 2.0.8 fix ensures strict boolean coercion and chain termination.

  • IP-based security controls need defense in depth: Even with this fix, applications should not rely solely on req.ip for critical access control. Combine IP checks with authentication, rate limiting per-user rather than per-IP, and logging of the full X-Forwarded-For chain for forensics.

  • Dependency version bumps in lockfiles matter: The change appears minimal—just a version number in package-lock.json—but the security impact is critical. Automated dependency updates that skip patch-level bumps for "trusted" packages miss vulnerabilities like CVE-2026-90711.

How Orbis AppSec Detected This

Source: The X-Forwarded-For HTTP request header, which proxy-addr reads via the forwarded dependency

Sink: The proxyaddr function's trust evaluation loop, specifically the address selection logic that returns the "most trusted" client IP

Missing control: Strict validation that the trust predicate result unambiguously terminates chain traversal; the 2.0.7 implementation allowed continued iteration when trust evaluation returned falsy-but-not-false values for certain address formats

CWE: unknown

Fix: Upgrade proxy-addr to 2.0.8, which hardens trust predicate evaluation and ensures conservative chain termination

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

CVE-2026-90711 in proxy-addr 2.0.7 demonstrates how even mature, widely-used middleware can harbor subtle trust evaluation flaws. The vulnerability's impact—IP spoofing that bypasses security controls—makes it critical for any Express.js application processing X-Forwarded-For headers. The 2.0.8 fix closes this vector without requiring application code changes, but developers should audit their use of req.ip and ensure they're not relying on client-reported addresses for security decisions that require stronger guarantees.

Prevention and further reading

Frequently Asked Questions

Does proxy-addr 2.0.8 change the default trust behavior for single-proxy deployments?

No, the default trust behavior remains unchanged. The fix specifically hardens evaluation when complex or contradictory X-Forwarded-* headers are present, closing the spoofing vector without breaking standard proxy configurations.

Can I mitigate CVE-2026-90711 by configuring Express's `trust proxy` setting without upgrading?

No. While proper `trust proxy` configuration reduces exposure, the vulnerability exists in proxy-addr's core trust evaluation logic that Express delegates to. Only upgrading to 2.0.8 fully eliminates the spoofing vector.

Does the 2.0.8 update include new OpenCollective funding metadata that affects runtime behavior?

No. The new `funding` field in package.json is metadata-only and has no runtime impact. The security fix is in the trust evaluation code, not related to the added funding configuration.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #35

Related Articles

high

CVE-2026-4800: lodash Template Imports Allow Code Execution

CVE-2026-4800 affects lodash's `_.template()` templating API, where untrusted input reaching the `imports` option can lead to arbitrary code execution. The fix was shipped as a dependency upgrade from lodash 4.17.21 to 4.18.1 in the project's lockfile, though the PR itself notes it was never verified against the actual code paths in this repository.

high

TweenMax `_applyCycle` Prototype Pollution via vars.cycle Keys

A bundled copy of the TweenMax animation library copied attacker-influenceable `vars.cycle` property names straight onto a tween configuration object using an unguarded `for...in` loop, so a key named `__proto__`, `constructor`, or `prototype` was written through to the object's prototype chain. The fix adds an explicit key denylist to both copies of the `_applyCycle` helper so those three names are skipped during the merge. No CVE or GHSA is assigned; the issue is tracked as CWE-1321 (Improperl

high

picomatch 2.3.1 ReDoS: Extglob Pattern Catastrophic Backtracking

picomatch versions below 2.3.2, 3.0.2, or 4.0.4 contain a Regular Expression Denial of Service vulnerability in extglob pattern parsing. An attacker can cause catastrophic backtracking with patterns containing nested alternations and quantifiers, freezing any Node.js process that evaluates untrusted glob expressions.

critical

pet-window.js Dynamic Code Evaluation: CWE-94 Hardening via Number

The pet-window module constructed dynamic JavaScript by embedding raw configuration values into code strings. An attacker with local access could inject arbitrary JavaScript by modifying stored configuration. The fix replaces string interpolation with explicit Number() coercion and NaN validation for all numeric configuration parameters.

high

sanitizeUnicodeInput(): Fullwidth U+ Bypasses Codepoint Validation

The `sanitizeUnicodeInput()` helper used by the project character-range settings screen rewrote `U+` prefixes to `0x` and called `parseInt()`, but never normalized its argument first. Compatibility-equivalent forms such as fullwidth `U+`, superscript digits, or mathematical alphanumerics never matched the `/U\+/gi` regex, fell through to the `else return inputString` branch, and were handed back to callers verbatim as "sanitized" values. The fix inserts a `String.prototype.normalize('NFKC')` pas

critical

JdbcSinkFunction.invoke() SQL Injection via Unvalidated Identifiers

The `JdbcSinkFunction.invoke()` method in Lacus's RTC engine built SQL INSERT statements with `String.format()`, placing database name, table name, and column names directly into the query string without validation. Although the actual row values used parameterized `?` placeholders, the identifiers themselves were injectable, letting a crafted sink configuration break out of the intended INSERT statement. The fix adds a strict allowlist regex that rejects any identifier containing characters out