Skip to content

Commit 8859c37

Browse files
committed
[Fix] parse: enforce arrayLimit on comma groups under []= when throwOnLimitExceeded is set
With `comma: true` and `throwOnLimitExceeded: true`, `parseArrayValue` only ran the pre-split comma count for flat values (`isFlatArrayValue`); a comma group under a `[]=` key was split unconditionally and then wrapped as a single nested element, so its length was never compared against `arrayLimit`, while `a=`, `a[0]=`, `a[b]=`, and `a.b=` all threw. The same gate also exempted object input. Drop the gate so every comma-split value is counted before splitting. An in-limit group under `a[]=` still counts as one element of the outer array, and the default (non-throwing) path is unchanged. This closes the `[]=` key form that the CVE-2026-2391 fix (f6a7abf) did not cover.
1 parent 8079adc commit 8859c37

2 files changed

Lines changed: 131 additions & 4 deletions

File tree

‎lib/parse.js‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ var interpretNumericEntities = function (str) {
3636
});
3737
};
3838

39-
var parseArrayValue = function (val, options, currentArrayLength, isFlatArrayValue) {
39+
var parseArrayValue = function (val, options, currentArrayLength) {
4040
if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) {
41-
if (isFlatArrayValue && options.throwOnLimitExceeded) {
41+
if (options.throwOnLimitExceeded) {
4242
var commaCount = 0;
4343
var commaIndex = val.indexOf(',');
4444
while (commaIndex > -1) {
@@ -125,8 +125,7 @@ var parseValues = function parseQueryStringValues(str, options) {
125125
parseArrayValue(
126126
part.slice(pos + 1),
127127
options,
128-
isArray(obj[key]) ? obj[key].length : 0,
129-
part.indexOf('[]=') === -1
128+
isArray(obj[key]) ? obj[key].length : 0
130129
),
131130
function (encodedVal) {
132131
return options.decoder(encodedVal, defaults.decoder, charset, 'value');

‎test/parse.js‎

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1589,6 +1589,134 @@ test('parse()', function (t) {
15891589
sst.end();
15901590
});
15911591

1592+
st.test('throws before splitting when a bracketed comma group exceeds arrayLimit', function (sst) {
1593+
sst['throws'](
1594+
function () {
1595+
qs.parse('a[]=1,2,3,4,5,6', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true });
1596+
},
1597+
new RangeError('Array limit exceeded. Only 5 elements allowed in an array.'),
1598+
'the inner group is itself an array and is subject to arrayLimit'
1599+
);
1600+
sst.end();
1601+
});
1602+
1603+
st.test('throws before splitting when a comma group under `[]=` exceeds arrayLimit', function (sst) {
1604+
sst['throws'](
1605+
function () {
1606+
qs.parse('a[]=1,2,3,4', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
1607+
},
1608+
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
1609+
'a bracket-push comma group over the limit throws like the flat form'
1610+
);
1611+
1612+
sst['throws'](
1613+
function () {
1614+
qs.parse('a[]=1,2', { comma: true, arrayLimit: 1, throwOnLimitExceeded: true });
1615+
},
1616+
new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
1617+
'singular message at arrayLimit 1'
1618+
);
1619+
1620+
sst['throws'](
1621+
function () {
1622+
qs.parse('a[b][]=1,2,3,4', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
1623+
},
1624+
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
1625+
'a nested bracket-push key throws'
1626+
);
1627+
1628+
sst['throws'](
1629+
function () {
1630+
qs.parse('a[][]=1,2,3,4', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
1631+
},
1632+
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
1633+
'a doubly bracket-pushed key throws'
1634+
);
1635+
1636+
sst['throws'](
1637+
function () {
1638+
qs.parse('a[]=5&a[]=1,2,3,4', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
1639+
},
1640+
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
1641+
'an oversized group appended after a scalar throws'
1642+
);
1643+
1644+
sst['throws'](
1645+
function () {
1646+
qs.parse('a[]=1,2,3,4&a[]=5', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
1647+
},
1648+
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
1649+
'an oversized group throws even when a later part would fit'
1650+
);
1651+
1652+
sst['throws'](
1653+
function () {
1654+
qs.parse('a[]=1,2,3,4&a[]=5', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true, duplicates: 'first' });
1655+
},
1656+
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
1657+
'throws with duplicates: first'
1658+
);
1659+
1660+
sst['throws'](
1661+
function () {
1662+
qs.parse('a[]=5&a[]=1,2,3,4', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true, duplicates: 'last' });
1663+
},
1664+
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
1665+
'throws with duplicates: last'
1666+
);
1667+
1668+
sst['throws'](
1669+
function () {
1670+
qs.parse({ a: '1,2,3,4' }, { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
1671+
},
1672+
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
1673+
'an oversized comma value in object input throws'
1674+
);
1675+
1676+
var big = 'a[]=' + new Array(1e5 + 1).join('1,') + '1';
1677+
sst['throws'](
1678+
function () {
1679+
qs.parse(big, { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
1680+
},
1681+
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
1682+
'a very long bracket-push comma group throws'
1683+
);
1684+
sst.end();
1685+
});
1686+
1687+
st.test('does not throw for a comma group under `[]=` within arrayLimit', function (sst) {
1688+
sst.deepEqual(
1689+
qs.parse('a[]=1,2,3', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true }),
1690+
{ a: [['1', '2', '3']] },
1691+
'an inner group exactly at the limit stays a single nested element'
1692+
);
1693+
sst.deepEqual(
1694+
qs.parse('a[]=1,2,3&a[]=4,5,6&a[]=7', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true }),
1695+
{ a: [['1', '2', '3'], ['4', '5', '6'], '7'] },
1696+
'each group counts as one element of the outer array'
1697+
);
1698+
sst.deepEqual(
1699+
qs.parse('a[b][]=1,2,3', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true }),
1700+
{ a: { b: [['1', '2', '3']] } },
1701+
'nested bracket-push key within the limit'
1702+
);
1703+
sst.deepEqual(
1704+
qs.parse({ a: '1,2,3' }, { comma: true, arrayLimit: 3, throwOnLimitExceeded: true }),
1705+
{ a: ['1', '2', '3'] },
1706+
'object input within the limit'
1707+
);
1708+
sst.end();
1709+
});
1710+
1711+
st.test('keeps an oversized comma group under `[]=` as a nested array without throwOnLimitExceeded', function (sst) {
1712+
sst.deepEqual(
1713+
qs.parse('a[]=1,2,3,4', { comma: true, arrayLimit: 3 }),
1714+
{ a: [['1', '2', '3', '4']] },
1715+
'the inner group is not converted to an overflow object'
1716+
);
1717+
sst.end();
1718+
});
1719+
15921720
st.test('throws for a bracketed comma group when arrayLimit is 0', function (sst) {
15931721
sst['throws'](
15941722
function () {

0 commit comments

Comments
 (0)