Back to Blog
high SEVERITY7 min read

How IPv4-mapped IPv6 addresses bypass rate limiting in Express.js and how to fix it

A critical vulnerability in express-rate-limit versions prior to 8.2.2 allowed attackers to bypass per-client rate limiting on dual-stack servers by exploiting incorrect IPv6 subnet masking. When IPv4 clients connected through IPv4-mapped IPv6 addresses (like ::ffff:192.0.2.1), the library failed to properly identify unique clients, enabling unlimited requests that could lead to denial of service. The fix upgrades express-rate-limit to 8.2.2 and its dependency ip-address to 10.1.0, implementing

O
By Orbis AppSec
Published August 7, 2026Reviewed August 7, 2026

Answer Summary

CVE-2026-30827 is an IPv6 subnet masking vulnerability (CWE-670) in express-rate-limit for Node.js that allows attackers to bypass per-client rate limits on dual-stack servers. IPv4-mapped IPv6 addresses (::ffff:x.x.x.x format) were incorrectly handled, preventing the library from distinguishing between unique clients and enabling denial of service attacks. The fix upgrades express-rate-limit from 8.2.1 to 8.2.2 and ip-address from 10.0.1 to 10.1.0, implementing proper IPv6 subnet masking to correctly identify and rate-limit individual IPv4 clients connecting through IPv6.

Vulnerability at a Glance

cweCWE-670 (Always-Incorrect Control Flow Implementation)
fixUpgrade express-rate-limit to 8.2.2 and ip-address to 10.1.0 for proper IPv6 handling
riskAttackers can bypass rate limits and launch denial of service attacks
languageJavaScript/Node.js
root causeIncorrect subnet masking for IPv4-mapped IPv6 addresses in ip-address 10.0.1
vulnerabilityIPv4-mapped IPv6 address bypass in rate limiting

Introduction

In a backend Express.js application, we discovered a high-severity rate limiting bypass in backend/package-lock.json affecting the express-rate-limit dependency version 8.2.1. This vulnerability, tracked as CVE-2026-30827, allowed attackers on dual-stack networks to completely bypass per-client rate limits by exploiting how the library handled IPv4-mapped IPv6 addresses. The flawed subnet masking logic in the underlying ip-address library (version 10.0.1) failed to correctly identify unique IPv4 clients when they connected through IPv6, treating multiple distinct clients as a single entity—or worse, failing to track them at all.

For applications relying on express-rate-limit to prevent brute force attacks, credential stuffing, or API abuse, this vulnerability created a critical security gap. An attacker could send unlimited requests from multiple IPv4 addresses, all appearing to the rate limiter as coming from the same (or untrackable) source, effectively nullifying the protection.

The Vulnerability Explained

The root cause lies in how express-rate-limit version 8.2.1 (and its dependency ip-address 10.0.1) processes client IP addresses on servers with dual-stack network configurations. Here's what happened:

When an IPv4 client connects to a dual-stack server (one supporting both IPv4 and IPv6), the connection may be represented as an IPv4-mapped IPv6 address. For example, the IPv4 address 192.0.2.1 becomes ::ffff:192.0.2.1 in IPv6 notation. This is standard behavior in dual-stack networking.

The vulnerable code in express-rate-limit 8.2.1 used ip-address version 10.0.1 to parse and normalize these addresses for rate limiting. However, the subnet masking logic in ip-address 10.0.1 incorrectly handled IPv4-mapped IPv6 addresses, leading to one of two failure modes:

  1. Client conflation: Multiple distinct IPv4 clients were treated as the same client, causing legitimate users to share rate limit quotas
  2. Client invisibility: IPv4-mapped addresses weren't properly tracked, allowing unlimited requests

Let's look at the vulnerable dependency specification from backend/package.json:

"express-rate-limit": "^8.2.1"

And the corresponding lock file entry in backend/package-lock.json:

"node_modules/express-rate-limit": {
  "version": "8.2.1",
  "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz",
  "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==",
  "license": "MIT",
  "dependencies": {
    "ip-address": "10.0.1"
  }
}

The problem is in the pinned dependency: ip-address: 10.0.1. This version contained the flawed subnet masking implementation.

Real-World Attack Scenario

Consider a backend API endpoint protected by express-rate-limit with a 100 requests/hour limit per client:

const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 60 * 60 * 1000, // 1 hour
  max: 100, // 100 requests per hour
  standardHeaders: true,
  legacyHeaders: false,
});

app.use('/api/', limiter);

An attacker on a dual-stack network could:

  1. Send 100 requests from IP 192.0.2.1 (appears as ::ffff:192.0.2.1)
  2. Send 100 more requests from IP 192.0.2.2 (appears as ::ffff:192.0.2.2)
  3. Send 100 more requests from IP 192.0.2.3 (appears as ::ffff:192.0.2.3)

Due to the incorrect subnet masking in ip-address 10.0.1, express-rate-limit might:
- Treat all three IPs as the same client (reaching the limit after just 100 requests, blocking legitimate users)
- Fail to track them properly (allowing all 300+ requests through, enabling DoS)

Both outcomes are security failures. The attacker could continue this pattern across an entire subnet, launching a denial of service attack against the API by either exhausting rate limits for legitimate users or flooding the server with unlimited requests.

The Fix

The fix involves upgrading two packages to versions with corrected IPv6 subnet masking logic:

  1. express-rate-limit: 8.2.1 → 8.2.2
  2. ip-address: 10.0.1 → 10.1.0 (transitive dependency)

Before (Vulnerable):

"express-rate-limit": "^8.2.1"
"node_modules/express-rate-limit": {
  "version": "8.2.1",
  "dependencies": {
    "ip-address": "10.0.1"
  }
}
"node_modules/ip-address": {
  "version": "10.0.1",
  "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
  "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA=="
}

After (Fixed):

"express-rate-limit": "^8.2.2"
"node_modules/express-rate-limit": {
  "version": "8.2.2",
  "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.2.tgz",
  "integrity": "sha512-Ybv7bqtOgA914MLwaHWVFXMpMYeR1MQu/D+z2MaLYteqBsTIp9sY3AU7mGNLMJv8eLg8uQMpE20I+L2Lv49nSg==",
  "dependencies": {
    "ip-address": "10.1.0"
  }
}
"node_modules/ip-address": {
  "version": "10.1.0",
  "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
  "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="
}

How This Fix Solves the Problem

The ip-address library version 10.1.0 implements correct subnet masking for IPv4-mapped IPv6 addresses. Specifically:

  • IPv4-mapped addresses like ::ffff:192.0.2.1 are now properly normalized and compared
  • The subnet masking algorithm correctly identifies unique /32 IPv4 addresses within the IPv4-mapped IPv6 space
  • Each distinct IPv4 client (192.0.2.1, 192.0.2.2, etc.) is now tracked separately by express-rate-limit
  • Rate limit quotas are enforced per actual client, not per incorrectly-masked subnet

This change was necessary in both files because:

  1. backend/package.json: Updates the direct dependency version constraint to ^8.2.2, ensuring future installs get the fixed version
  2. backend/package-lock.json: Locks both express-rate-limit to 8.2.2 AND ip-address to 10.1.0, guaranteeing the correct transitive dependency is installed

The fix maintains full backward compatibility—all legitimate client requests continue to work exactly as before. The only change is that rate limiting now works correctly on dual-stack networks, properly identifying and tracking individual IPv4 clients.

Prevention & Best Practices

1. Keep Rate Limiting Libraries Updated

Rate limiting is a critical security control. Treat rate limiting dependencies with the same urgency as authentication libraries:

# Regularly audit and update security-sensitive packages
npm audit
npm update express-rate-limit

2. Test Rate Limiting on Dual-Stack Networks

If your infrastructure supports both IPv4 and IPv6, explicitly test rate limiting behavior:

// Test with IPv4-mapped IPv6 addresses
const testIPs = [
  '::ffff:192.0.2.1',
  '::ffff:192.0.2.2',
  '::ffff:192.0.2.3'
];

// Verify each IP is tracked separately

3. Monitor Rate Limiter Effectiveness

Implement monitoring to detect rate limiter bypass attempts:

const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 60 * 60 * 1000,
  max: 100,
  handler: (req, res) => {
    // Log rate limit violations for security monitoring
    console.warn(`Rate limit exceeded for IP: ${req.ip}`);
    res.status(429).json({ error: 'Too many requests' });
  }
});

4. Use Dependency Scanning in CI/CD

Integrate tools like Trivy, Snyk, or npm audit into your pipeline:

# .github/workflows/security.yml
- name: Run Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@master
  with:
    scan-type: 'fs'
    scan-ref: '.'
    format: 'sarif'
    output: 'trivy-results.sarif'

5. Consider Defense in Depth

Don't rely solely on application-level rate limiting:

  • Network-level rate limiting: Use load balancers (AWS ALB, Nginx) for additional protection
  • WAF rules: Cloud WAFs can provide IP-based rate limiting before requests reach your app
  • Connection limits: Configure reverse proxies to limit connections per IP

6. Follow OWASP Guidelines

Refer to OWASP API Security Top 10 - API4:2023 Unrestricted Resource Consumption for comprehensive rate limiting strategies.

Key Takeaways

  • IPv4-mapped IPv6 addresses (::ffff:x.x.x.x) can bypass rate limiting when libraries incorrectly handle subnet masking on dual-stack servers
  • express-rate-limit 8.2.1 with ip-address 10.0.1 contained flawed subnet masking logic that failed to distinguish between unique IPv4 clients connecting through IPv6
  • Upgrading to express-rate-limit 8.2.2 (with ip-address 10.1.0) fixes the subnet masking algorithm, ensuring each IPv4 client is correctly identified and rate-limited
  • Dual-stack network configurations are increasingly common in cloud environments, making this vulnerability likely to be exploitable in production systems
  • Rate limiting bypass vulnerabilities can enable denial of service attacks, brute force attempts, and API abuse that directly impact application availability and security

How Orbis AppSec Detected This

  • Source: HTTP client connections on dual-stack network interfaces, where IPv4 addresses are represented as IPv4-mapped IPv6 addresses (::ffff:x.x.x.x)
  • Sink: ip-address library version 10.0.1 subnet masking logic called by express-rate-limit 8.2.1 in backend/package-lock.json
  • Missing control: Correct IPv6 subnet masking algorithm for IPv4-mapped addresses, allowing client identification bypass
  • CWE: CWE-670 (Always-Incorrect Control Flow Implementation)
  • Fix: Upgraded express-rate-limit to 8.2.2 and ip-address to 10.1.0, implementing correct subnet masking for IPv4-mapped IPv6 addresses

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-30827 demonstrates how subtle networking issues can create serious security vulnerabilities. The incorrect handling of IPv4-mapped IPv6 addresses in express-rate-limit 8.2.1 could have allowed attackers to bypass rate limits entirely, enabling denial of service attacks and API abuse. By upgrading to version 8.2.2 with the fixed ip-address 10.1.0 dependency, applications can ensure that rate limiting works correctly on modern dual-stack networks.

This vulnerability highlights the importance of keeping security-critical dependencies updated, testing rate limiting behavior across different network configurations, and implementing defense-in-depth strategies. Rate limiting is often the first line of defense against automated attacks—when it fails, the entire application becomes vulnerable.

References

Frequently Asked Questions

What is IPv4-mapped IPv6 address bypass in rate limiting?

It's a vulnerability where rate limiting middleware fails to correctly identify unique clients when IPv4 addresses are represented in IPv6 format (::ffff:192.0.2.1), allowing attackers to bypass per-client request limits by exploiting incorrect subnet masking logic.

How do you prevent IPv4-mapped IPv6 bypass in Express.js rate limiting?

Upgrade express-rate-limit to version 8.2.2 or later (8.1.1 for 8.1.x branch, 8.0.2 for 8.0.x branch), which includes the fixed ip-address library 10.1.0 that correctly handles IPv4-mapped IPv6 subnet masking for dual-stack network environments.

What CWE is IPv4-mapped IPv6 rate limit bypass?

This vulnerability maps to CWE-670 (Always-Incorrect Control Flow Implementation) because the subnet masking logic incorrectly processed IPv4-mapped IPv6 addresses, leading to improper client identification and rate limit enforcement failures.

Is using only IPv4 addresses enough to prevent this bypass?

No. On dual-stack servers (supporting both IPv4 and IPv6), IPv4 clients may connect through IPv4-mapped IPv6 addresses automatically. The only complete fix is upgrading to express-rate-limit 8.2.2+ with proper IPv6 handling, not disabling IPv6 entirely.

Can static analysis detect IPv4-mapped IPv6 rate limit bypass?

Yes. Dependency scanners like Trivy can detect vulnerable versions of express-rate-limit (< 8.2.2, < 8.1.1, < 8.0.2) by matching CVE-2026-30827. However, detecting the logic flaw requires understanding IPv6 subnet masking behavior in the ip-address library.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #4

Related Articles

high

How Path Traversal and Security Policy Bypass Happens in Node.js Dependencies and How to Fix It

A high-severity vulnerability in the fast-uri package (CVE-2026-6321) allowed attackers to bypass security policies through improper Unicode hostname canonicalization and path traversal. This issue affected the @apralabs/apra-fleet project through its dependency tree, and was resolved by upgrading fast-uri from version 3.1.0 to 4.1.2 using npm overrides.

high

How Command Injection happens in Node.js child_process calls and how to fix it

A high-severity command injection vulnerability was discovered in `tools/utils/lang/helpers.ts` where the `prettier()` function passed a user-controllable `fileName` argument directly into a shell command string via `exec()`. By replacing `exec()` with `execFile()` and passing arguments as an array, the fix eliminates shell interpolation entirely, preventing attackers from injecting arbitrary shell commands through malicious filenames.

high

How Quadratic CPU Consumption in YAML Parsing happens in JavaScript and how to fix it

A high-severity vulnerability in js-yaml versions 3.x and 4.x allowed attackers to cause quadratic CPU consumption through specially crafted YAML documents using the `!!omap` type. This denial-of-service vulnerability (GHSA-5p4m-2wfm-xmqj) was fixed by upgrading from js-yaml 4.3.0 to 4.3.1, protecting applications from algorithmic complexity attacks during YAML parsing.

high

How Arbitrary HTTP Header Injection via Prototype Pollution happens in JavaScript and how to fix it

A high-severity vulnerability (CVE-2026-42035) in axios version 1.13.5 allowed attackers to inject arbitrary HTTP headers through prototype pollution. The fix upgrades axios to version 1.18.0 in the frontend's dependency tree, which includes proper prototype chain validation when constructing HTTP request headers. This prevents attackers from manipulating outgoing requests to perform SSRF, session hijacking, or cache poisoning attacks.

high

How Infinite Loop Denial of Service happens in nanoid custom alphabet generation and how to fix it

A high-severity infinite loop vulnerability (CVE-2026-67213) was discovered in nanoid versions before 5.1.6 and 3.3.17, affecting the custom alphabet generation feature. When processing certain malformed alphabet configurations, nanoid would enter an infinite loop, causing a complete denial of service. This vulnerability was fixed by upgrading from nanoid 3.3.16 to 3.3.17 and implementing dependency overrides to ensure the patched version is used throughout the dependency tree.

high

How Command Injection Happens in Node.js child_process and How to Fix It

A high-severity command injection vulnerability was discovered in `scripts/common.js` where the `exec()` function used `execSync()` with unsanitized input, allowing potential command injection attacks. The fix replaces `execSync()` with `execFileSync()` and separates command arguments into an array, preventing shell metacharacter interpretation. This defensive hardening removes an exploit primitive that could be chained with other weaknesses by automated attack tools.