CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-2391
6.30.05%

Death by a Thousand Commas: Deep Dive into CVE-2026-2391

Alon Barad
Alon Barad
Software Engineer

Feb 12, 2026·6 min read·26 visits

PoC Available

Executive Summary (TL;DR)

The `qs` library (used by Express.js) ignores `arrayLimit` when parsing comma-separated values (`?a=1,2,3...`). Attackers can trigger OOM crashes by sending massive comma strings. Fixed in 6.14.2.

A logic flaw in the popular Node.js `qs` library allows attackers to bypass array limits when the `comma` parsing option is enabled. By sending a crafted query string containing thousands of commas, an unauthenticated attacker can force the application to allocate massive arrays, leading to memory exhaustion and a Denial of Service (DoS). This vulnerability highlights the dangers of 'return early' patterns in input validation logic.

The Hook: Parsing is Hard

If you've ever written a Node.js web server, you've almost certainly used qs. It's the de facto standard for parsing query strings, sitting underneath heavyweights like Express.js. It turns the chaotic soup of URL parameters (?foo=bar&baz=qux) into nice, clean JavaScript objects. It handles deep nesting, arrays, and all the edge cases that the native querystring module ignores.

But here's the thing about parsing libraries: they are the frontline defense. They touch user input before your application logic even wakes up. If the parser is broken, your fancy authentication middleware doesn't matter. You are already dead.

CVE-2026-2391 targets a specific convenience feature in qs: Comma-Separated Values. Sometimes developers want ?ids=1,2,3 to automatically become ['1', '2', '3'] without forcing the client to send ?ids[]=1&ids[]=2.... To support this, qs offers the comma: true option. It sounds innocent enough, but as we're about to see, enabling this option essentially handed attackers a 'Skip Logic' card for security controls.

The Flaw: Logic Amnesia

The vulnerability isn't a complex buffer overflow or a heap grooming masterpiece. It's a classic case of Order of Operations failure. Developers often implement security limits—like arrayLimit—to prevent memory exhaustion. In qs, the default arrayLimit is 20. If you try to send 100 items, qs is supposed to stop parsing or convert the excess into an object index to save memory.

However, in lib/parse.js, the logic for handling commas was implemented before the limit checks were fully enforced. The code looked at the input string, saw a comma, and immediately thought, "I know what to do here! I'll split this string and return the result right now!"

> [!NOTE] > The Fatal Mistake: The code returned the result of val.split(',') immediately. It bypassed the subsequent code blocks responsible for checking if the resulting array size exceeded the configured limit.

This means that even if you configured qs to throw an error when limits are exceeded (throwOnLimitExceeded: true), the comma parser would happily generate an array of 1,000,000 items before the validator ever got a chance to object. It’s like a bouncer checking ID at the front door, but leaving the loading dock wide open for anyone carrying a box.

The Code: The Smoking Gun

Let's look at the diff. This is where the oversight becomes painful to read. The vulnerability lived in the parseValues function.

The Vulnerable Code (Simplified):

// lib/parse.js (Pre-patch)
var parseValues = function (str, options) {
    // ... setup ...
 
    // IF commas are enabled and present...
    if (options.comma && val.indexOf(',') > -1) {
        // ... SPLIT AND RETURN IMMEDIATELY
        return val.split(',');
    }
 
    // ... complex parsing logic and LIMIT CHECKS happen down here ...
};

See that return? That's the game over. The function exits before it ever reaches the code that says, "Hey, is this array too big?"

The Fix (Commit f6a7abf):

The maintainers fixed this by removing the early return and explicitly checking the length of the generated array against the arrayLimit.

// lib/parse.js (Patched)
if (options.comma && val.indexOf(',') > -1) {
    var values = val.split(',');
    // Explicitly check the limit on the split result
    if (values.length > options.arrayLimit) {
        if (options.throwOnLimitExceeded) {
            throw new RangeError('Array limit exceeded...');
        }
        // Or safely combine/truncate
        values = utils.combine([], values, options.arrayLimit, options.plainObjects);
    }
    return values;
}

The fix forces the comma-split array to undergo the same scrutiny as any other array structure.

The Exploit: DoS via Allocation

Exploiting this is trivially easy if you can find an endpoint where comma: true is enabled. You don't need special headers, you don't need to bypass a WAF (usually), and you don't need authentication.

The Attack Scenario:

  1. Recon: The attacker identifies a target using qs (common in Node.js apps). They fuzz parameters with commas (?q=a,b) to see if the server treats them as arrays.
  2. Weaponization: The attacker constructs a query string containing a single parameter with millions of commas.
  3. Execution: They send the request. The Node.js event loop blocks while String.prototype.split allocates a massive array of strings on the heap.

Proof of Concept:

const qs = require('qs');
 
// Target configuration
const options = {
    comma: true,
    arrayLimit: 5, // We expect this to stop us... but it won't.
    throwOnLimitExceeded: true
};
 
// The payload: 1 million commas
const attackPayload = 'data=' + 'a,'.repeat(1000000);
 
console.log("Attempting parse...");
try {
    const result = qs.parse(attackPayload, options);
    console.log(`Success! Array length: ${result.data.length}`);
    console.log("The limit of 5 was completely ignored.");
} catch (e) {
    console.log("Blocked:", e.message);
}

When run against a vulnerable version, this script outputs Array length: 1000001. The memory usage spikes instantly. If an attacker sends concurrent requests like this, the Node.js process will hit its memory limit (default ~1.5GB) and crash with FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory.

The Impact: Why It Matters

You might argue, "Who enables comma: true anyway?" It's less common than the default behavior, but it is frequently used in APIs that want to support compact filter lists (e.g., GET /users?ids=1,2,3).

The consequences are:

  • Availability Loss: A single attacker can crash the server (DoS). Since Node.js is often deployed with process managers (like PM2) that restart crashes, the attacker can put the server into a continuous crash loop, effectively taking it offline.
  • Resource Cost: For serverless environments (AWS Lambda, etc.), this causes massive CPU spikes and max memory usage, driving up the bill even if the function eventually times out.
  • Latency: Even if the server doesn't crash, the Garbage Collector (GC) will go into overdrive trying to clean up these massive string arrays, causing significant latency for legitimate users.

This is a classic Asymmetric Attack: It costs the attacker fractions of a penny to generate a string of commas, but it costs the defender significant CPU cycles and RAM to process it.

The Fix: Mitigation & Defense

The remediation is straightforward, but it requires action. This isn't a vulnerability that will magically go away.

Immediate Steps:

  1. Patch: Update qs to 6.14.2 or higher. Run npm audit fix or manually upgrade your package.json.
  2. Configuration Review: Search your codebase for comma: true. If you don't actually need comma-separated parsing, turn it off. The safest code is code that doesn't run.
  3. Input Validation: Regardless of the library fix, you should validate the length of input strings at the ingress point (e.g., Nginx, AWS WAF). Reject query strings that are unreasonably long (e.g., > 2KB) before they ever reach your Node.js application.

> [!TIP] > Developer Lesson: Never assume a library enforces its own constraints consistently across all features. When enabling "convenience" features (like fuzzy matching or comma parsing), always verify if the standard security limits still apply.

Official Patches

GitHubOfficial patch commit

Fix Analysis (1)

Technical Appendix

CVSS Score
6.3/ 10
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N
EPSS Probability
0.05%
Top 85% most exploited

Affected Systems

Node.js applications using `qs`Express.js applications (if using `qs` with custom configuration)APIs parsing CSV-style query parameters

Affected Versions Detail

Product
Affected Versions
Fixed Version
qs
ljharb
<= 6.14.16.14.2
AttributeDetail
CWECWE-20 / CWE-770
CVSS v4.06.3 (Medium)
Attack VectorNetwork (Remote)
PrivilegesNone
ImpactDenial of Service (Memory Exhaustion)
EPSS Score0.00049 (~0.05%)

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1499Endpoint Denial of Service
Impact
CWE-770
Allocation of Resources Without Limits or Throttling

Improper Input Validation leading to Allocation of Resources Without Limits

Known Exploits & Detection

Internal ResearchPoC demonstrating memory spike with comma-separated values

Vulnerability Timeline

Fix committed to GitHub
2026-02-10
CVE and GHSA Published
2026-02-12

References & Sources

  • [1]GitHub Security Advisory
  • [2]NVD CVE-2026-2391

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.