qs's arrayLimit bypass in comma parsing allows denial of service
Description
Summary
The arrayLimit option in qs does not enforce limits for comma-separated values when comma: true is enabled, allowing attackers to cause denial-of-service via memory exhaustion. This is a bypass of the array limit enforcement, similar to the bracket notation bypass addressed in GHSA-6rw7-vpxm-498p (CVE-2025-15284).
Details
When the comma option is set to true (not the default, but configurable in applications), qs allows parsing comma-separated strings as arrays (e.g., ?param=a,b,c becomes ['a', 'b', 'c']). However, the limit check for arrayLimit (default: 20) and the optional throwOnLimitExceeded occur after the comma-handling logic in parseArrayValue, enabling a bypass. This permits creation of arbitrarily large arrays from a single parameter, leading to excessive memory allocation.
Vulnerable code (lib/parse.js: lines ~40-50): ``js if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) { return val.split(','); } if (options.throwOnLimitExceeded && currentArrayLength >= options.arrayLimit) { throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.'); } return val; ``
The split(',') returns the array immediately, skipping the subsequent limit check. Downstream merging via utils.combine does not prevent allocation, even if it marks overflows for sparse arrays.This discrepancy allows attackers to send a single parameter with millions of commas (e.g., ?param=,,,,,,,,...), allocating massive arrays in memory without triggering limits. It bypasses the intent of arrayLimit, which is enforced correctly for indexed (a[0]=) and bracket (a[]=) notations (the latter fixed in v6.14.1 per GHSA-6rw7-vpxm-498p).
PoC
Test 1 - Basic bypass: `` npm install qs ``
const qs = require('qs');
const payload = 'a=' + ','.repeat(25); // 26 elements after split (bypasses arrayLimit: 5)
const options = { comma: true, arrayLimit: 5, throwOnLimitExceeded: true };
try {
const result = qs.parse(payload, options);
console.log(result.a.length); // Outputs: 26 (bypass successful)
} catch (e) {
console.log('Limit enforced:', e.message); // Not thrown
}
Configuration: - comma: true - arrayLimit: 5 - throwOnLimitExceeded: true
Expected: Throws "Array limit exceeded" error. Actual: Parses successfully, creating an array of length 26.
Impact
Denial of Service (DoS) via memory exhaustion.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
qs fails to enforce the `arrayLimit` option on comma-separated values when `comma: true` is enabled, allowing memory exhaustion via a single crafted parameter.
Root
Cause
The qs library provides an arrayLimit option (default: 20) to cap the number of elements parsed into an array from a query string. However, when the comma option is enabled (non-default), the parseArrayValue function splits the value on commas *before* the limit check occurs. The vulnerable code at lib/parse.js immediately returns the result of val.split(','), skipping the subsequent throwOnLimitExceeded and array length comparisons [1][2]. This allows an attacker to supply a single parameter with an arbitrarily long comma-separated string (e.g., ?a=,,,...) and bypass the intended limit.
Attack
Vector
No authentication is required; the vulnerability is triggered simply by sending an HTTP request that includes a crafted query string parameter with comma: true configured on the server. The attacker does not need to be authenticated or have a privileged network position — they only need to be able to deliver such a request to an application that uses qs with the comma option enabled. A proof of concept demonstrates that a parameter containing 25 commas (producing 26 elements) bypasses an arrayLimit of 5 [3]. The memory allocated by splitting millions of commas can rapidly exhaust available memory, leading to a denial of service.
Impact
Successful exploitation results in denial of service through memory exhaustion. The library allocates an array of arbitrary size based on the number of commas in the input, potentially consuming all available memory on the server and causing the application or host to crash or become unresponsive. This is a bypass of the same class of issue as GHSA-6rw7-vpxm-498p (CVE-2025-15284), which addressed a similar bypass for bracket notation [2][3].
Mitigation
The issue has been patched in commit f6a7abff [4]. The fix moves the arrayLimit enforcement to occur *before* the comma split, ensuring that values exceeding the limit are either converted to an object (if throwOnLimitExceeded is false) or cause a RangeError to be thrown. Users should update to a version of qs that includes this commit or apply the patch manually. Applications that do not set comma: true are not affected.
AI Insight generated on May 19, 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.
| Package | Affected versions | Patched versions |
|---|---|---|
qsnpm | >= 6.7.0, < 6.14.2 | 6.14.2 |
Patches
1f6a7abff1f13[Fix] `parse`: enforce `arrayLimit` on `comma`-parsed values
2 files changed · +43 −0
lib/parse.js+7 −0 modified@@ -130,6 +130,13 @@ var parseValues = function parseQueryStringValues(str, options) { val = isArray(val) ? [val] : val; } + if (options.comma && isArray(val) && val.length > options.arrayLimit) { + if (options.throwOnLimitExceeded) { + throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.'); + } + val = utils.combine([], val, options.arrayLimit, options.plainObjects); + } + if (key !== null) { var existing = has.call(obj, key); if (existing && options.duplicates === 'combine') {
test/parse.js+36 −0 modified@@ -1349,6 +1349,42 @@ test('arrayLimit boundary conditions', function (t) { t.end(); }); +test('comma + arrayLimit', function (t) { + t.test('comma-separated values within arrayLimit stay as array', function (st) { + var result = qs.parse('a=1,2,3', { comma: true, arrayLimit: 5 }); + st.ok(Array.isArray(result.a), 'result is an array'); + st.deepEqual(result.a, ['1', '2', '3'], 'all values present'); + st.end(); + }); + + t.test('comma-separated values exceeding arrayLimit convert to object', function (st) { + var result = qs.parse('a=1,2,3,4', { comma: true, arrayLimit: 3 }); + st.notOk(Array.isArray(result.a), 'result is not an array when over limit'); + st.deepEqual(result.a, { 0: '1', 1: '2', 2: '3', 3: '4' }, 'all values preserved as object'); + st.end(); + }); + + t.test('comma-separated values exceeding arrayLimit with throwOnLimitExceeded throws', function (st) { + st['throws']( + function () { + qs.parse('a=1,2,3,4', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true }); + }, + new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'), + 'throws error when comma-split exceeds array limit' + ); + st.end(); + }); + + t.test('comma-separated values at exactly arrayLimit stay as array', function (st) { + var result = qs.parse('a=1,2,3', { comma: true, arrayLimit: 3 }); + st.ok(Array.isArray(result.a), 'result is an array when exactly at limit'); + st.deepEqual(result.a, ['1', '2', '3'], 'all values present'); + st.end(); + }); + + t.end(); +}); + test('mixed array and object notation', function (t) { t.test('array brackets with object key - under limit', function (st) { st.deepEqual(
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/ljharb/qs/commit/f6a7abff1f13d644db9b05fe4f2c98ada6bf8482ghsapatchWEB
- github.com/advisories/GHSA-w7fw-mjwx-w883ghsaADVISORY
- github.com/ljharb/qs/security/advisories/GHSA-w7fw-mjwx-w883ghsavendor-advisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2026-2391ghsaADVISORY
News mentions
0No linked articles in our index yet.