VYPR
Low severityNVD Advisory· Published Feb 12, 2026· Updated Feb 12, 2026

qs's arrayLimit bypass in comma parsing allows denial of service

CVE-2026-2391

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.

PackageAffected versionsPatched versions
qsnpm
>= 6.7.0, < 6.14.26.14.2

Patches

1
f6a7abff1f13

[Fix] `parse`: enforce `arrayLimit` on `comma`-parsed values

https://github.com/ljharb/qsJordan HarbandFeb 10, 2026via ghsa
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

News mentions

0

No linked articles in our index yet.