Skip to content

fix: prevent IPv4-mapped IPv6 trust subnets from matching all IPv4 (GHSA-jqcg-44mw-7w3h) - #117

Merged
mcollina merged 1 commit into
mainfrom
fix/ipv4-mapped-ipv6-trust-subnet
Sep 15, 2026
Merged

mcollina merged 1 commit into
mainfrom
fix/ipv4-mapped-ipv6-trust-subnet

Conversation

@mcollina

Copy link
Copy Markdown
Member

Summary

Ports the upstream proxy-addr 2.0.8 remediation for GHSA-jqcg-44mw-7w3h / CVE-2026-90711 (severity critical, CVSS 9.1).

An IPv4-mapped IPv6 trust subnet with a short prefix (e.g. ::ffff:10.0.0.0/8) compiled with all-zero leading bits and matched every IPv4 address instead of the block it named. That trusted any unauthenticated client as a proxy at hop 0, so proxyaddr() — and therefore req.ip/req.ips in Express — returned the attacker-supplied X-Forwarded-For value, defeating IP-based access control, rate limiting, geolocation and audit logging.

Repro (before fix)

proxyaddr({ socket: { remoteAddress: '1.1.1.1' }, headers: { 'x-forwarded-for': '6.6.6.6' } }, '::ffff:10.0.0.0/8')
// => '6.6.6.6'  <- arbitrary host trusted at hop 0; XFF believed

After the fix this returns 1.1.1.1, and the correct spelling ::ffff:10.0.0.0/104 (or plain 10.0.0.0/8) continues to behave correctly.

Changes

  • index.js
    • Canonicalize IPv4-mapped candidate addresses to IPv4 up front, so a mapped address cannot bypass the cross-family guard via the same-family path.
    • An IPv6 trust subnet now only spans IPv4 when it is a genuine IPv4-mapped subnet and its prefix covers the ::ffff: marker (range ≥ 96); short prefixes like /8 or ::/1 no longer match IPv4.
    • A native IPv6 candidate can no longer match an IPv4-mapped subnet.
  • test/base.test.js — regression tests for the short-prefix mapped subnet, ::/1, and native-IPv6-vs-mapped-subnet cases for both the single and multi-subnet trust paths.

The conversion was simplified to only produce an IPv4-mapped address because, after the up-front canonicalization, a cross-family IPv4 subnet is handled via the same-family path; this keeps the enforced 100% branch coverage intact.

Verification

  • npm test — 75 unit tests pass, 100% statement/branch/function/line coverage
  • npm run lint — clean
  • npm run test:typescript — pass

Resolves the vulnerability in this fork (note: the advisory is filed against the upstream proxy-addr package and does not enumerate this fork, but the vulnerable code was inherited).

An IPv4-mapped IPv6 trust subnet with a short prefix, such as
`::ffff:10.0.0.0/8`, compiled with all-zero leading bits and matched every
IPv4 address instead of the block it named. This trusted any unauthenticated
client as a proxy at hop 0, so proxyaddr() (and req.ip/req.ips in Express)
returned the attacker-controlled X-Forwarded-For value, defeating IP-based
access control, rate limiting, geolocation and audit logging.

Port the upstream proxy-addr 2.0.8 remediation:
- canonicalize IPv4-mapped candidates to IPv4 up front, so a mapped address
  cannot bypass the cross-family guard via the same-family path
- an IPv6 trust subnet only spans IPv4 when it is a genuine IPv4-mapped
  subnet whose prefix covers the ::ffff: marker (range >= 96)
- a native IPv6 candidate can no longer match an IPv4-mapped subnet

Ref: GHSA-jqcg-44mw-7w3h / CVE-2026-90711

@ivan-tymoshenko ivan-tymoshenko left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@mcollina
mcollina merged commit 847d138 into main Sep 15, 2026
20 checks passed
@mcollina
mcollina deleted the fix/ipv4-mapped-ipv6-trust-subnet branch September 15, 2026 16:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants