Skip to content

Conversation

@renovate
Copy link
Contributor

@renovate renovate bot commented Dec 31, 2025

This PR contains the following updates:

Package Change Age Confidence
qs 6.5.36.14.1 age confidence

GitHub Vulnerability Alerts

CVE-2025-15284

Summary

The arrayLimit option in qs does not enforce limits for bracket notation (a[]=1&a[]=2), allowing attackers to cause denial-of-service via memory exhaustion. Applications using arrayLimit for DoS protection are vulnerable.

Details

The arrayLimit option only checks limits for indexed notation (a[0]=1&a[1]=2) but completely bypasses it for bracket notation (a[]=1&a[]=2).

Vulnerable code (lib/parse.js:159-162):

if (root === '[]' && options.parseArrays) {
    obj = utils.combine([], leaf);  // No arrayLimit check
}

Working code (lib/parse.js:175):

else if (index <= options.arrayLimit) {  // Limit checked here
    obj = [];
    obj[index] = leaf;
}

The bracket notation handler at line 159 uses utils.combine([], leaf) without validating against options.arrayLimit, while indexed notation at line 175 checks index <= options.arrayLimit before creating arrays.

PoC

Test 1 - Basic bypass:

npm install qs
const qs = require('qs');
const result = qs.parse('a[]=1&a[]=2&a[]=3&a[]=4&a[]=5&a[]=6', { arrayLimit: 5 });
console.log(result.a.length);  // Output: 6 (should be max 5)

Test 2 - DoS demonstration:

const qs = require('qs');
const attack = 'a[]=' + Array(10000).fill('x').join('&a[]=');
const result = qs.parse(attack, { arrayLimit: 100 });
console.log(result.a.length);  // Output: 10000 (should be max 100)

Configuration:

  • arrayLimit: 5 (test 1) or arrayLimit: 100 (test 2)
  • Use bracket notation: a[]=value (not indexed a[0]=value)

Impact

Denial of Service via memory exhaustion. Affects applications using qs.parse() with user-controlled input and arrayLimit for protection.

Attack scenario:

  1. Attacker sends HTTP request: GET /api/search?filters[]=x&filters[]=x&...&filters[]=x (100,000+ times)
  2. Application parses with qs.parse(query, { arrayLimit: 100 })
  3. qs ignores limit, parses all 100,000 elements into array
  4. Server memory exhausted → application crashes or becomes unresponsive
  5. Service unavailable for all users

Real-world impact:

  • Single malicious request can crash server
  • No authentication required
  • Easy to automate and scale
  • Affects any endpoint parsing query strings with bracket notation

Suggested Fix

Add arrayLimit validation to the bracket notation handler. The code already calculates currentArrayLength at line 147-151, but it's not used in the bracket notation handler at line 159.

Current code (lib/parse.js:159-162):

if (root === '[]' && options.parseArrays) {
    obj = options.allowEmptyArrays && (leaf === '' || (options.strictNullHandling && leaf === null))
        ? []
        : utils.combine([], leaf);  // No arrayLimit check
}

Fixed code:

if (root === '[]' && options.parseArrays) {
    // Use currentArrayLength already calculated at line 147-151
    if (options.throwOnLimitExceeded && currentArrayLength >= options.arrayLimit) {
        throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
    }
    
    // If limit exceeded and not throwing, convert to object (consistent with indexed notation behavior)
    if (currentArrayLength >= options.arrayLimit) {
        obj = options.plainObjects ? { __proto__: null } : {};
        obj[currentArrayLength] = leaf;
    } else {
        obj = options.allowEmptyArrays && (leaf === '' || (options.strictNullHandling && leaf === null))
            ? []
            : utils.combine([], leaf);
    }
}

This makes bracket notation behaviour consistent with indexed notation, enforcing arrayLimit and converting to object when limit is exceeded (per README documentation).


Release Notes

ljharb/qs (qs)

v6.14.1

Compare Source

  • [Fix] ensure arrayLength applies to [] notation as well
  • [Fix] parse: when a custom decoder returns null for a key, ignore that key
  • [Refactor] parse: extract key segment splitting helper
  • [meta] add threat model
  • [actions] add workflow permissions
  • [Tests] stringify: increase coverage
  • [Dev Deps] update eslint, @ljharb/eslint-config, npmignore, es-value-fixtures, for-each, object-inspect

v6.14.0

Compare Source

  • [New] parse: add throwOnParameterLimitExceeded option (#​517)
  • [Refactor] parse: use utils.combine more
  • [patch] parse: add explicit throwOnLimitExceeded default
  • [actions] use shared action; re-add finishers
  • [meta] Fix changelog formatting bug
  • [Deps] update side-channel
  • [Dev Deps] update es-value-fixtures, has-bigints, has-proto, has-symbols
  • [Tests] increase coverage

v6.13.1

Compare Source

  • [Fix] stringify: avoid a crash when a filter key is null
  • [Fix] utils.merge: functions should not be stringified into keys
  • [Fix] parse: avoid a crash with interpretNumericEntities: true, comma: true, and iso charset
  • [Fix] stringify: ensure a non-string filter does not crash
  • [Refactor] use __proto__ syntax instead of Object.create for null objects
  • [Refactor] misc cleanup
  • [Tests] utils.merge: add some coverage
  • [Tests] fix a test case
  • [actions] split out node 10-20, and 20+
  • [Dev Deps] update es-value-fixtures, mock-property, object-inspect, tape

v6.13.0

Compare Source

  • [New] parse: add strictDepth option (#​511)
  • [Tests] use npm audit instead of aud

v6.12.3

Compare Source

  • [Fix] parse: properly account for strictNullHandling when allowEmptyArrays
  • [meta] fix changelog indentation

v6.12.2

Compare Source

  • [Fix] parse: parse encoded square brackets (#​506)
  • [readme] add CII best practices badge

v6.12.1

Compare Source

  • [Fix] parse: Disable decodeDotInKeys by default to restore previous behavior (#​501)
  • [Performance] utils: Optimize performance under large data volumes, reduce memory usage, and speed up processing (#​502)
  • [Refactor] utils: use +=
  • [Tests] increase coverage

v6.12.0

Compare Source

  • [New] parse/stringify: add decodeDotInKeys/encodeDotKeys options (#​488)
  • [New] parse: add duplicates option
  • [New] parse/stringify: add allowEmptyArrays option to allow [] in object values (#​487)
  • [Refactor] parse/stringify: move allowDots config logic to its own variable
  • [Refactor] stringify: move option-handling code into normalizeStringifyOptions
  • [readme] update readme, add logos (#​484)
  • [readme] stringify: clarify default arrayFormat behavior
  • [readme] fix line wrapping
  • [readme] remove dead badges
  • [Deps] update side-channel
  • [meta] make the dist build 50% smaller
  • [meta] add sideEffects flag
  • [meta] run build in prepack, not prepublish
  • [Tests] parse: remove useless tests; add coverage
  • [Tests] stringify: increase coverage
  • [Tests] use mock-property
  • [Tests] stringify: improve coverage
  • [Dev Deps] update @ljharb/eslint-config , aud, has-override-mistake, has-property-descriptors, mock-property, npmignore, object-inspect, tape
  • [Dev Deps] pin glob, since v10.3.8+ requires a broken jackspeak
  • [Dev Deps] pin jackspeak since 2.1.2+ depends on npm aliases, which kill the install process in npm < 6

v6.11.2

Compare Source

  • [Fix] parse: Fix parsing when the global Object prototype is frozen (#​473)
  • [Tests] add passing test cases with empty keys (#​473)

v6.11.1

Compare Source

  • [Fix] stringify: encode comma values more consistently (#​463)
  • [readme] add usage of filter option for injecting custom serialization, i.e. of custom types (#​447)
  • [meta] remove extraneous code backticks (#​457)
  • [meta] fix changelog markdown
  • [actions] update checkout action
  • [actions] restrict action permissions
  • [Dev Deps] update @ljharb/eslint-config, aud, object-inspect, tape

v6.11.0

Compare Source

  • [New] [Fix] stringify: revert 0e903c0; add commaRoundTrip option (#​442)
  • [readme] fix version badge

v6.10.5

Compare Source

  • [Fix] stringify: with arrayFormat: comma, properly include an explicit [] on a single-item array (#​434)

v6.10.4

Compare Source

  • [Fix] stringify: with arrayFormat: comma, include an explicit [] on a single-item array (#​441)
  • [meta] use npmignore to autogenerate an npmignore file
  • [Dev Deps] update eslint, @ljharb/eslint-config, aud, has-symbol, object-inspect, tape

v6.10.3

Compare Source

  • [Fix] parse: ignore __proto__ keys (#​428)
  • [Robustness] stringify: avoid relying on a global undefined (#​427)
  • [actions] reuse common workflows
  • [Dev Deps] update eslint, @ljharb/eslint-config, object-inspect, tape

v6.10.2

Compare Source

  • [Fix] stringify: actually fix cyclic references (#​426)
  • [Fix] stringify: avoid encoding arrayformat comma when encodeValuesOnly = true (#​424)
  • [readme] remove travis badge; add github actions/codecov badges; update URLs
  • [Docs] add note and links for coercing primitive values (#​408)
  • [actions] update codecov uploader
  • [actions] update workflows
  • [Tests] clean up stringify tests slightly
  • [Dev Deps] update eslint, @ljharb/eslint-config, aud, object-inspect, safe-publish-latest, tape

v6.10.1

Compare Source

  • [Fix] stringify: avoid exception on repeated object values (#​402)

v6.10.0

Compare Source

  • [New] stringify: throw on cycles, instead of an infinite loop (#​395, #​394, #​393)
  • [New] parse: add allowSparse option for collapsing arrays with missing indices (#​312)
  • [meta] fix README.md (#​399)
  • [meta] only run npm run dist in publish, not install
  • [Dev Deps] update eslint, @ljharb/eslint-config, aud, has-symbols, tape
  • [Tests] fix tests on node v0.6
  • [Tests] use ljharb/actions/node/install instead of ljharb/actions/node/run
  • [Tests] Revert "[meta] ignore eclint transitive audit warning"

v6.9.7

Compare Source

  • [Fix] parse: ignore __proto__ keys (#​428)
  • [Fix] stringify: avoid encoding arrayformat comma when encodeValuesOnly = true (#​424)
  • [Robustness] stringify: avoid relying on a global undefined (#​427)
  • [readme] remove travis badge; add github actions/codecov badges; update URLs
  • [Docs] add note and links for coercing primitive values (#​408)
  • [Tests] clean up stringify tests slightly
  • [meta] fix README.md (#​399)
  • Revert "[meta] ignore eclint transitive audit warning"
  • [actions] backport actions from main
  • [Dev Deps] backport updates from main

v6.9.6

Compare Source

  • [Fix] restore dist dir; mistakenly removed in d4f6c32

v6.9.5

Compare Source

  • [Fix] stringify: do not encode parens for RFC1738
  • [Fix] stringify: fix arrayFormat comma with empty array/objects (#​350)
  • [Refactor] format: remove util.assign call
  • [meta] add "Allow Edits" workflow; update rebase workflow
  • [actions] switch Automatic Rebase workflow to pull_request_target event
  • [Tests] stringify: add tests for #​378
  • [Tests] migrate tests to Github Actions
  • [Tests] run nyc on all tests; use tape runner
  • [Dev Deps] update eslint, @ljharb/eslint-config, browserify, mkdirp, object-inspect, tape; add aud

v6.9.4

Compare Source

  • [Fix] stringify: when arrayFormat is comma, respect serializeDate (#​364)
  • [Refactor] stringify: reduce branching (part of #​350)
  • [Refactor] move maybeMap to utils
  • [Dev Deps] update browserify, tape

v6.9.3

Compare Source

  • [Fix] proper comma parsing of URL-encoded commas (#​361)
  • [Fix] parses comma delimited array while having percent-encoded comma treated as normal text (#​336)

v6.9.2

Compare Source

  • [Fix] parse: Fix parsing array from object with comma true (#​359)
  • [Fix] parse: throw a TypeError instead of an Error for bad charset (#​349)
  • [meta] ignore eclint transitive audit warning
  • [meta] fix indentation in package.json
  • [meta] add tidelift marketing copy
  • [Dev Deps] update eslint, @ljharb/eslint-config, object-inspect, has-symbols, tape, mkdirp, iconv-lite
  • [actions] add automatic rebasing / merge commit blocking

v6.9.1

Compare Source

  • [Fix] parse: with comma true, handle field that holds an array of arrays (#​335)
  • [Fix] parse: with comma true, do not split non-string values (#​334)
  • [meta] add funding field
  • [Dev Deps] update eslint, @ljharb/eslint-config
  • [Tests] use shared travis-ci config

v6.9.0

Compare Source

  • [New] parse/stringify: Pass extra key/value argument to decoder (#​333)
  • [Dev Deps] update eslint, @ljharb/eslint-config, evalmd
  • [Tests] parse: add passing arrayFormat tests
  • [Tests] add posttest using npx aud to run npm audit without a lockfile
  • [Tests] up to node v12.10, v11.15, v10.16, v8.16
  • [Tests] Buffer.from in node v5.0-v5.9 and v4.0-v4.4 requires a TypedArray

v6.8.3

Compare Source

  • [Fix] parse: ignore __proto__ keys (#​428)
  • [Robustness] stringify: avoid relying on a global undefined (#​427)
  • [Fix] stringify: avoid encoding arrayformat comma when encodeValuesOnly = true (#​424)
  • [readme] remove travis badge; add github actions/codecov badges; update URLs
  • [Tests] clean up stringify tests slightly
  • [Docs] add note and links for coercing primitive values (#​408)
  • [meta] fix README.md (#​399)
  • [actions] backport actions from main
  • [Dev Deps] backport updates from main
  • [Refactor] stringify: reduce branching
  • [meta] do not publish workflow files

v6.8.2

Compare Source

  • [Fix] proper comma parsing of URL-encoded commas (#​361)
  • [Fix] parses comma delimited array while having percent-encoded comma treated as normal text (#​336)

v6.8.1

Compare Source

  • [Fix] parse: Fix parsing array from object with comma true (#​359)
  • [Fix] parse: throw a TypeError instead of an Error for bad charset (#​349)
  • [Fix] parse: with comma true, handle field that holds an array of arrays (#​335)
  • [fix] parse: with comma true, do not split non-string values (#​334)
  • [meta] add tidelift marketing copy
  • [meta] add funding field
  • [Dev Deps] update eslint, @ljharb/eslint-config, tape, safe-publish-latest, evalmd, has-symbols, iconv-lite, mkdirp, object-inspect
  • [Tests] parse: add passing arrayFormat tests
  • [Tests] use shared travis-ci configs
  • [Tests] Buffer.from in node v5.0-v5.9 and v4.0-v4.4 requires a TypedArray
  • [actions] add automatic rebasing / merge commit blocking

v6.8.0

Compare Source

  • [New] add depth=false to preserve the original key; [Fix] depth=0 should preserve the original key (#​326)
  • [New] [Fix] stringify symbols and bigints
  • [Fix] ensure node 0.12 can stringify Symbols
  • [Fix] fix for an impossible situation: when the formatter is called with a non-string value
  • [Refactor] formats: tiny bit of cleanup.
  • [Dev Deps] update eslint, @ljharb/eslint-config, browserify, safe-publish-latest, iconv-lite, tape
  • [Tests] add tests for depth=0 and depth=false behavior, both current and intuitive/intended (#​326)
  • [Tests] use eclint instead of editorconfig-tools
  • [docs] readme: add security note
  • [meta] add github sponsorship
  • [meta] add FUNDING.yml
  • [meta] Clean up license text so it’s properly detected as BSD-3-Clause

v6.7.3

Compare Source

  • [Fix] parse: ignore __proto__ keys (#​428)
  • [Fix] stringify: avoid encoding arrayformat comma when encodeValuesOnly = true (#​424)
  • [Robustness] stringify: avoid relying on a global undefined (#​427)
  • [readme] remove travis badge; add github actions/codecov badges; update URLs
  • [Docs] add note and links for coercing primitive values (#​408)
  • [meta] fix README.md (#​399)
  • [meta] do not publish workflow files
  • [actions] backport actions from main
  • [Dev Deps] backport updates from main
  • [Tests] use nyc for coverage
  • [Tests] clean up stringify tests slightly

v6.7.2

Compare Source

  • [Fix] proper comma parsing of URL-encoded commas (#​361)
  • [Fix] parses comma delimited array while having percent-encoded comma treated as normal text (#​336)

v6.7.1

Compare Source

  • [Fix] parse: Fix parsing array from object with comma true (#​359)
  • [Fix] parse: with comma true, handle field that holds an array of arrays (#​335)
  • [fix] parse: with comma true, do not split non-string values (#​334)
  • [Fix] parse: throw a TypeError instead of an Error for bad charset (#​349)
  • [Fix] fix for an impossible situation: when the formatter is called with a non-string value
  • [Refactor] formats: tiny bit of cleanup.
  • readme: add security note
  • [meta] add tidelift marketing copy
  • [meta] add funding field
  • [meta] add FUNDING.yml
  • [meta] Clean up license text so it’s properly detected as BSD-3-Clause
  • [Dev Deps] update eslint, @ljharb/eslint-config, tape, safe-publish-latest, evalmd, iconv-lite, mkdirp, object-inspect, browserify
  • [Tests] parse: add passing arrayFormat tests
  • [Tests] use shared travis-ci configs
  • [Tests] Buffer.from in node v5.0-v5.9 and v4.0-v4.4 requires a TypedArray
  • [Tests] add tests for depth=0 and depth=false behavior, both current and intuitive/intended
  • [Tests] use eclint instead of editorconfig-tools
  • [actions] add automatic rebasing / merge commit blocking

v6.7.0

Compare Source

  • [New] stringify/parse: add comma as an arrayFormat option (#​276, #​219)
  • [Fix] correctly parse nested arrays (#​212)
  • [Fix] utils.merge: avoid a crash with a null target and a truthy non-array source, also with an array source
  • [Robustness] stringify: cache Object.prototype.hasOwnProperty
  • [Refactor] utils: isBuffer: small tweak; add tests
  • [Refactor] use cached Array.isArray
  • [Refactor] parse/stringify: make a function to normalize the options
  • [Refactor] utils: reduce observable [[Get]]s
  • [Refactor] stringify/utils: cache Array.isArray
  • [Tests] always use String(x) over x.toString()
  • [Tests] fix Buffer tests to work in node < 4.5 and node < 5.10
  • [Tests] temporarily allow coverage to fail

v6.6.1

Compare Source

  • [Fix] parse: ignore __proto__ keys (#​428)
  • [Fix] fix for an impossible situation: when the formatter is called with a non-string value
  • [Fix] utils.merge: avoid a crash with a null target and an array source
  • [Fix] utils.merge: avoid a crash with a null target and a truthy non-array source
  • [Fix] correctly parse nested arrays
  • [Robustness] stringify: avoid relying on a global undefined (#​427)
  • [Robustness] stringify: cache Object.prototype.hasOwnProperty
  • [Refactor] formats: tiny bit of cleanup.
  • [Refactor] utils: isBuffer: small tweak; add tests
  • [Refactor]: stringify/utils: cache Array.isArray
  • [Refactor] utils: reduce observable [[Get]]s
  • [Refactor] use cached Array.isArray
  • [Refactor] parse/stringify: make a function to normalize the options
  • [readme] remove travis badge; add github actions/codecov badges; update URLs
  • [Docs] Clarify the need for "arrayLimit" option
  • [meta] fix README.md (#​399)
  • [meta] do not publish workflow files
  • [meta] Clean up license text so it’s properly detected as BSD-3-Clause
  • [meta] add FUNDING.yml
  • [meta] Fixes typo in CHANGELOG.md
  • [actions] backport actions from main
  • [Tests] fix Buffer tests to work in node < 4.5 and node < 5.10
  • [Tests] always use String(x) over x.toString()
  • [Dev Deps] backport from main

v6.6.0

Compare Source

  • [New] Add support for iso-8859-1, utf8 "sentinel" and numeric entities (#​268)
  • [New] move two-value combine to a utils function (#​189)
  • [Fix] stringify: fix a crash with strictNullHandling and a custom filter/serializeDate (#​279)
  • [Fix] when parseArrays is false, properly handle keys ending in [] (#​260)
  • [Fix] stringify: do not crash in an obscure combo of interpretNumericEntities, a bad custom decoder, & iso-8859-1
  • [Fix] utils: merge: fix crash when source is a truthy primitive & no options are provided
  • [refactor] stringify: Avoid arr = arr.concat(...), push to the existing instance (#​269)
  • [Refactor] parse: only need to reassign the var once
  • [Refactor] parse/stringify: clean up charset options checking; fix defaults
  • [Refactor] add missing defaults
  • [Refactor] parse: one less concat call
  • [Refactor] utils: compactQueue: make it explicitly side-effecting
  • [Dev Deps] update browserify, eslint, @ljharb/eslint-config, iconv-lite, safe-publish-latest, tape
  • [Tests] up to node v10.10, v9.11, v8.12, v6.14, v4.9; pin included builds to LTS

Configuration

📅 Schedule: Branch creation - "" (UTC), Automerge - At any time (no schedule defined).

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant