VYPR
High severityNVD Advisory· Published Mar 7, 2026· Updated Mar 9, 2026

express-rate-limit: IPv4-mapped IPv6 addresses bypass per-client rate limiting (all IPv4 clients share one bucket on dual-stack servers)

CVE-2026-30827

Description

express-rate-limit is a basic rate-limiting middleware for Express. In versions starting from 8.0.0 and prior to versions 8.0.2, 8.1.1, 8.2.2, and 8.3.0, the default keyGenerator in express-rate-limit applies IPv6 subnet masking (/56 by default) to all addresses that net.isIPv6() returns true for. This includes IPv4-mapped IPv6 addresses (::ffff:x.x.x.x), which Node.js returns as request.ip on dual-stack servers. Because the first 80 bits of all IPv4-mapped addresses are zero, a /56 (or any /32 to /80) subnet mask produces the same network key (::/56) for every IPv4 client. This collapses all IPv4 traffic into a single rate-limit bucket: one client exhausting the limit causes HTTP 429 for all other IPv4 clients. This issue has been patched in versions 8.0.2, 8.1.1, 8.2.2, and 8.3.0.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

express-rate-limit incorrectly applies IPv6 subnet masking to IPv4-mapped IPv6 addresses, collapsing all IPv4 traffic into one rate-limit bucket.

Vulnerability

Overview

The default keyGenerator in express-rate-limit (versions 8.0.0 through 8.0.1, 8.1.0, 8.2.0–8.2.1, and 8.3.0-rc.0) applies IPv6 subnet masking (/56 by default) to any address for which net.isIPv6() returns true. This includes IPv4-mapped IPv6 addresses (::ffff:x.x.x.x), which Node.js returns as request.ip on dual-stack servers. Because the first 80 bits of all IPv4-mapped addresses are zero, a /56 (or any /32 to /80) subnet mask produces the same network key (::/56) for every IPv4 client [1][4].

Exploitation

Conditions

An attacker does not need authentication; the vulnerability is triggered automatically on any dual-stack Express server that uses the default keyGenerator. When a single IPv4 client exhausts the rate limit (e.g., by sending many requests), the server returns HTTP 429 (Too Many Requests) for all other IPv4 clients, because they all share the same rate-limit bucket key (::/56) [1][4]. The attack surface is any public endpoint protected by this middleware.

Impact

This bug effectively disables per-IPv4-client rate limiting, allowing one malicious or misconfigured client to deny service to all other IPv4 users. It does not affect IPv6 clients, which are still correctly bucketed by their /56 subnet. The issue is a denial-of-service (DoS) vector that can be triggered without special privileges [1][4].

Mitigation

The fix is included in versions 8.0.2, 8.1.1, 8.2.2, and 8.3.0. The patch (commit 14e5388) adds a check for IPv4-mapped addresses using the ip-address library: if the address is IPv4-mapped, it extracts and returns the plain IPv4 address, bypassing subnet masking [3]. Users should upgrade to one of the patched versions or apply a custom keyGenerator that handles IPv4-mapped addresses correctly [2][4].

AI Insight generated on May 18, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
express-rate-limitnpm
>= 8.2.0, < 8.2.28.2.2
express-rate-limitnpm
>= 8.1.0, < 8.1.18.1.1
express-rate-limitnpm
>= 8.0.0, < 8.0.28.0.2

Affected products

2

Patches

1
14e53888cdfd

fix: handle ipv4 mapped to ipv6 (ghsa-46wh-pxpv-q5gq)

3 files changed · +28 5
  • package.json+3 3 modified
    @@ -73,6 +73,9 @@
     		"pre-commit": "lint-staged",
     		"prepare": "run-s compile && husky"
     	},
    +	"dependencies": {
    +		"ip-address": "10.1.0"
    +	},
     	"peerDependencies": {
     		"express": ">= 4.11"
     	},
    @@ -105,8 +108,5 @@
     	"lint-staged": {
     		"*.{js,ts,json}": "biome check --write",
     		"*.{md,yaml}": "prettier --write"
    -	},
    -	"dependencies": {
    -		"ip-address": "10.1.0"
     	}
     }
    
  • source/ip-key-generator.ts+14 2 modified
    @@ -17,9 +17,21 @@ import { Address6 } from 'ip-address'
      * @public
      */
     export function ipKeyGenerator(ip: string, ipv6Subnet: number | false = 56) {
    -	if (ipv6Subnet && isIPv6(ip)) {
    +	if (isIPv6(ip)) {
    +		const address = new Address6(ip)
    +
    +		// First, check if the address is IPv4 mapped to IPv6 (e.g., ::ffff:x.y.z.w),
    +		// as is common on servers with dual-stack networks (both IPv4 and IPv6). If
    +		// this is the case, we extract and return the IPv4 address. Otherwise, the
    +		// default subnet value of 56 (or any 32 to 80 subnet) ignores the unique IP
    +		// address in the last two octets completely.
    +		if (address.is4()) return address.to4().correctForm()
    +
     		// For IPv6, return the network address of the subnet in CIDR format
    -		return `${new Address6(`${ip}/${ipv6Subnet}`).startAddress().correctForm()}/${ipv6Subnet}`
    +		if (ipv6Subnet) {
    +			const subnet = new Address6(`${ip}/${ipv6Subnet}`)
    +			return `${subnet.startAddress().correctForm()}/${ipv6Subnet}`
    +		}
     	}
     
     	// For IPv4, just return the IP address itself
    
  • test/library/ip-key-generator-test.ts+11 0 modified
    @@ -6,12 +6,23 @@ describe('ipKeyGenerator', () => {
     		expect(ipKeyGenerator('1.2.3.4', 16)).toBe('1.2.3.4')
     	})
     
    +	it('should return an IPv4 address mapped to IPv6 as an IPv4 address', () => {
    +		expect(ipKeyGenerator('::ffff:1.2.3.4')).toBe('1.2.3.4')
    +		expect(ipKeyGenerator('::1.2.3.4')).toBe('1.2.3.4')
    +	})
    +
     	it('should return an IPv6 address unchanged with ipv6Subnet set to false', () => {
     		expect(
     			ipKeyGenerator('0123:4567:89ab:cdef:0123:4567:89ab:cdef', false),
     		).toBe('0123:4567:89ab:cdef:0123:4567:89ab:cdef')
     	})
     
    +	it('should apply ipv6Subnet only a true IPv6 address', () => {
    +		expect(ipKeyGenerator('::1.2.3.4', 16)).toBe('1.2.3.4')
    +		expect(ipKeyGenerator('::ffff:1.2.3.4', 16)).toBe('1.2.3.4')
    +		expect(ipKeyGenerator('::1.2.3.4', false)).toBe('1.2.3.4')
    +	})
    +
     	it('should apply a default /56 netmask to an IPv6 address', () => {
     		expect(ipKeyGenerator('0123:4567:89ab:cdef:0123:4567:89ab:cdef')).toBe(
     			'123:4567:89ab:cd00::/56',
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.