You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

qs

Package Overview
Dependencies
Maintainers
2
Versions
146
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

qs - npm Package Compare versions

Comparing version
6.9.8
to
6.9.9
+5
-0
CHANGELOG.md

@@ -0,1 +1,6 @@

## **6.9.9**
- [Fix] fix regressions from robustness refactor
- [meta] add `npmignore` to autogenerate an npmignore file
- [actions] update reusable workflows
## **6.9.8**

@@ -2,0 +7,0 @@ - [Robustness] avoid `.push`, use `void`

+2
-2

@@ -236,3 +236,3 @@ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Qs = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){

if (segment) {
keys[keys.length] = '[' + key.slice(segment.index + ']');
keys[keys.length] = '[' + key.slice(segment.index) + ']';
}

@@ -599,3 +599,3 @@

for (var i = 0; i < 256; ++i) {
array[array.length] = '%' + ((i < 16 ? '0' : '' + i.toString(16)).toUpperCase());
array[array.length] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase();
}

@@ -602,0 +602,0 @@

@@ -197,3 +197,3 @@ 'use strict';

if (segment) {
keys[keys.length] = '[' + key.slice(segment.index + ']');
keys[keys.length] = '[' + key.slice(segment.index) + ']';
}

@@ -200,0 +200,0 @@

@@ -11,3 +11,3 @@ 'use strict';

for (var i = 0; i < 256; ++i) {
array[array.length] = '%' + ((i < 16 ? '0' : '' + i.toString(16)).toUpperCase());
array[array.length] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase();
}

@@ -14,0 +14,0 @@

@@ -5,3 +5,3 @@ {

"homepage": "https://github.com/ljharb/qs",
"version": "6.9.8",
"version": "6.9.9",
"repository": {

@@ -50,6 +50,7 @@ "type": "git",

"safer-buffer": "^2.1.2",
"tape": "^5.4.0"
"tape": "^5.4.0",
"npmignore": "^0.3.1"
},
"scripts": {
"prepublishOnly": "safe-publish-latest && npm run dist",
"prepublishOnly": "safe-publish-latest && npmignore --auto --commentLines=autogenerated && npm run dist",
"prepublish": "not-in-publish || npm run prepublishOnly",

@@ -71,3 +72,11 @@ "pretest": "npm run --silent readme && npm run --silent lint",

]
},
"publishConfig": {
"ignore": [
"!dist/*",
"bower.json",
"component.json",
".github/workflows"
]
}
}

@@ -87,2 +87,11 @@ 'use strict';

t.test('correctly computes the remainder when depth is exceeded', function (st) {
st.deepEqual(
qs.parse('a[b][c][d][e]=f', { depth: 2 }),
{ a: { b: { c: { '[d][e]': 'f' } } } },
'the remainder is "[d][e]", not the full original key'
);
st.end();
});
t.test('uses original key when depth = 0', function (st) {

@@ -89,0 +98,0 @@ st.deepEqual(qs.parse('a[0]=b&a[1]=c', { depth: 0 }), { 'a[0]': 'b', 'a[1]': 'c' });

@@ -24,2 +24,8 @@ 'use strict';

t.test('correctly encodes low-byte characters', function (st) {
st.equal(qs.stringify({ a: String.fromCharCode(1) }), 'a=%01', 'encodes 0x01');
st.equal(qs.stringify({ a: String.fromCharCode(15) }), 'a=%0F', 'encodes 0x0F');
st.end();
});
t.test('stringifies falsy values', function (st) {

@@ -26,0 +32,0 @@ st.equal(qs.stringify(undefined), '');

# Backport Record
Commits to backport from main (v6.14.1..v6.14.2, excluding version bump):
| # | SHA | Description |
|---|-----|-------------|
| 1 | 6744d30 | [Robustness] avoid `.push`, use `void` |
| 2 | 6bdfaf5 | [readme] replace runkit/travis CI badge with shields.io check-runs badge |
| 3 | 2a35775 | [meta] fix changelog typo |
| 4 | 1b9a8b4 | [actions] fix rebase workflow permissions |
| 5 | fbc5206 | [Fix] `parse`: fix error message |
| 6 | f6a7abf | [Fix] `parse`: enforce arrayLimit on comma-parsed values |
| 7 | febb644 | [Fix] `parse`: throw on arrayLimit exceeded with indexed notation |
| 8 | cfc108f | [Fix] arrayLimit means max count, not max index |
| 9 | 6addf8c | [Fix] `parse`: mark overflow objects for indexed notation |
| 10 | 5c308e5 | [readme] clarify parseArrays and arrayLimit documentation |
| 11 | 294db90 | [readme] document addQueryPrefix does not add ? to empty output |
## Results
| Version | Base Tag | Base SHA | Result HEAD | Applied | Skipped |
|---------|----------|----------|-------------|---------|---------|
| 6.14 | v6.14.2 | bdcf0c7 | bdcf0c7 | all | — |
| 6.13 | v6.13.1 | f1ee037 | 625fa19 | 1,2,4,10,11 | 3,5,6,7,8,9 |
| 6.12 | v6.12.3 | f90cc35 | 8a1b294 | 1,2,4,10,11 | 3,5,6,7,8,9 |
| 6.11 | v6.11.2 | 410bdd3 | 3a5f714 | 1,2,4,10,11 | 3,5,6,7,8,9 |
| 6.10 | v6.10.5 | 95bc018 | 8189da8 | 1,2,4,10,11 | 3,5,6,7,8,9 |
| 6.9 | v6.9.7 | 4cd0032 | 6a7a3bf | 1,2,4,10,11 | 3,5,6,7,8,9 |
| 6.8 | v6.8.3 | 0db5538 | 76d53e9 | 1,2,4,10,11 | 3,5,6,7,8,9 |
| 6.7 | v6.7.3 | 834389a | 2991d8b | 1,2,4,10,11 | 3,5,6,7,8,9 |
| 6.6 | v6.6.1 | 4cc653c | 7093153 | 1,2,4,10,11 | 3,5,6,7,8,9 |
| 6.5 | v6.5.3 | 298bfa5 | 40b77c3 | 1,2,4,10,11 | 3,5,6,7,8,9 |
| 6.4 | v6.4.1 | 486aa46 | d9ffe2b | 1,2,4,10 | 3,5,6,7,8,9,11 |
| 6.3 | v6.3.3 | ff235b4 | b4824cb | 1,2,4,10 | 3,5,6,7,8,9,11 |
| 6.2 | v6.2.4 | 90d9f2b | db7b937 | 1,2,4,10 | 3,5,6,7,8,9,11 |
| 6.1 | v6.1.2 | 68ca039 | 3245e1f | 1,2,10 | 3,4,5,6,7,8,9,11 |
| 6.0 | v6.0.4 | 10233c9 | b779be4 | 1,2,10 | 3,4,5,6,7,8,9,11 |
## Legend for Applied/Skipped columns
1=Robustness, 2=readme badge, 3=changelog typo, 4=actions permissions,
5=error msg fix, 6=comma arrayLimit, 7=indexed throw, 8=count semantics,
9=mark overflow, 10=parseArrays/arrayLimit docs, 11=addQueryPrefix docs
## Notes
- #2 (badge): v6.4+ had runkit badge replaced; v6.0-v6.3 had Travis badge replaced
- #4 (actions): v6.2-v6.10 had permissions block ADDED (not modified); v6.0-v6.1 have no rebase.yml
## Skip Reasons
| Commit | Reason for skip |
|--------|----------------|
| 3 (2a35775) | v6.14.1-specific changelog — not applicable to other versions |
| 5 (fbc5206) | Requires `throwOnLimitExceeded` option (v6.14.0+) |
| 6 (f6a7abf) | Requires overflow system (`combine(a,b,arrayLimit,plainObjects)`) from v6.14.1 |
| 7 (febb644) | Requires `throwOnLimitExceeded` option (v6.14.0+) |
| 8 (cfc108f) | Requires overflow system from v6.14.1 |
| 9 (6addf8c) | Requires overflow system from v6.14.1 |
| 11 (294db90) | v6.4 and below lack `addQueryPrefix` in README |
## Release Tags
| Tag | SHA | Changelog Entries |
|-----|-----|-------------------|
| v6.13.2 | d8a8ab3 | Robustness, readme #543, readme #418, readme badge, actions |
| v6.12.4 | a67173e | Robustness, readme #543, readme #418, readme badge, actions |
| v6.11.3 | 6302f35 | Robustness, readme #543, readme #418, readme badge, actions |
| v6.10.6 | 1aa4bd9 | Robustness, readme #543, readme #418, readme badge, actions |
| v6.9.8 | 479d4b1 | Robustness, readme #543, readme #418, readme badge, actions |
| v6.8.4 | 0f2b1e2 | Robustness, readme #543, readme #418, readme badge, actions |
| v6.7.4 | d38e43a | Robustness, readme #543, readme #418, readme badge, actions |
| v6.6.2 | 5e1c72c | Robustness, readme #543, readme #418, readme badge, actions |
| v6.5.4 | c190488 | Robustness, readme #543, readme #418, readme badge, actions |
| v6.4.2 | 9b50144 | Robustness, readme #543, readme badge (runkit+travis), actions |
| v6.3.4 | aa3f9f4 | Robustness, readme #543, readme badge (travis), actions |
| v6.2.5 | d68f354 | Robustness, readme #543, readme badge (travis), actions |
| v6.1.3 | cb45ba1 | Robustness, readme #543, readme badge (travis) |
| v6.0.5 | a73f299 | Robustness, readme #543, readme badge (travis) |
# GHSA-2856-wc6q-8xwc — Comment to post
This is not a vulnerability. The described behavior only modifies properties on the **result object** returned by `qs.parse()` — it does not modify `Object.prototype` or any shared state. Setting `toString` or `hasOwnProperty` on a specific result object is not prototype pollution; it's the expected outcome of parsing a key with that name.
The `allowPrototypes` option exists precisely to control whether keys that shadow `Object.prototype` properties are allowed on result objects. When `depth <= 0` and brackets are present, the prototype check is applied to the bracketed form of the key rather than the stripped form — this is a minor inconsistency in the check, but the result is equivalent to what would happen with `allowPrototypes: true`, which is an explicitly supported configuration.
Closing as not applicable.
# GHSA-6rw7-vpxm-498p — Edits to make
## Severity
Change: High → Low
## CVSS 3.1
Old: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H` (7.5 High)
New: `CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L` (3.7 Low)
Changes:
- AC: L → H (requires non-default parameterLimit config for real impact)
- A: H → L (bounded memory increase, not a crash/full DoS under defaults)
## Description — replace with:
### Summary
The `arrayLimit` option in qs did not enforce limits for bracket notation (`a[]=1&a[]=2`), only for indexed notation (`a[0]=1`). This is a consistency bug; `arrayLimit` should apply uniformly across all array notations.
**Note:** The default `parameterLimit` of 1000 effectively mitigates the DoS scenario originally described. With default options, bracket notation cannot produce arrays larger than `parameterLimit` regardless of `arrayLimit`, because each `a[]=value` consumes one parameter slot. The severity has been reduced accordingly.
### Details
The `arrayLimit` option only checked limits for indexed notation (`a[0]=1&a[1]=2`) but did not enforce it for bracket notation (`a[]=1&a[]=2`).
**Vulnerable code** (`lib/parse.js:159-162`):
```javascript
if (root === '[]' && options.parseArrays) {
obj = utils.combine([], leaf); // No arrayLimit check
}
```
**Working code** (`lib/parse.js:175`):
```javascript
else if (index <= options.arrayLimit) { // Limit checked here
obj = [];
obj[index] = leaf;
}
```
### PoC
```javascript
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)
```
**Note on parameterLimit interaction:** The original advisory's "DoS demonstration" claimed a length of 10,000, but `parameterLimit` (default: 1000) caps parsing to 1,000 parameters. With default options, the actual output is 1,000, not 10,000.
### Impact
Consistency bug in `arrayLimit` enforcement. With default `parameterLimit`, the practical DoS risk is negligible since `parameterLimit` already caps the total number of parsed parameters (and thus array elements from bracket notation). The risk increases only when `parameterLimit` is explicitly set to a very high value.
# GHSA-w7fw-mjwx-w883 — Comment to post
This is a valid report — thank you. The comma-split code path in `parseArrayValue` returned the split array immediately, bypassing the `arrayLimit` check. Unlike the bracket notation case (GHSA-6rw7-vpxm-498p), this genuinely bypasses `parameterLimit` as well, since a single parameter like `a=x,x,x,...` produces an arbitrarily large array from one `&`-delimited parameter.
A fix has been implemented: after the comma split and decode step, if the resulting array exceeds `arrayLimit`, it is converted to an object (consistent with other `arrayLimit` exceedances), and `throwOnLimitExceeded` is respected.
A few corrections to the advisory:
- **Severity should be Medium, not High**: This requires the non-default `comma: true` option to be explicitly enabled. HTTP server request size limits also bound the practical impact.
- **Vulnerable version range**: Should be `<= 6.14.1` (not `< 6.14.1`), since 6.14.1 is also affected.
- **Patched version**: Will be set once the fix is released.
{
"permissions": {
"allow": [
"Bash(npm run lint:*)",
"Bash(npm test:*)",
"Bash(npm run tests-only:*)",
"Bash(npm show:*)",
"Bash(git log:*)",
"Bash(git tag:*)",
"Bash(npm view:*)",
"Bash(git fetch:*)",
"Bash(git cat-file:*)",
"Bash(git show:*)",
"Bash(git rev-parse:*)",
"Bash(git rev-list:*)",
"Bash(gh api:*)",
"Bash(node /tmp/qs-vuln-test.js:*)",
"Bash(git -C /Users/ljharb/git/ljharb/qs.git show 3086902 --stat)",
"Bash(git -C /Users/ljharb/git/ljharb/qs.git diff HEAD -- lib/parse.js lib/utils.js)",
"Bash(open:*)",
"WebSearch",
"WebFetch(domain:www.first.org)",
"WebFetch(domain:nvd.nist.gov)",
"WebFetch(domain:raw.githubusercontent.com)",
"Bash(git -C /Users/ljharb/git/ljharb/qs.git diff origin/main...HEAD)",
"Bash(for ver in v6.13.1 v6.12.3 v6.11.2 v6.10.5 v6.9.7 v6.8.3 v6.7.3 v6.6.1 v6.5.3 v6.4.1 v6.3.3 v6.2.4 v6.1.2 v6.0.4)",
"Bash(do echo '=== $ver ===')",
"Bash(done)",
"Bash(git checkout:*)",
"Bash(cd /Users/ljharb/git/ljharb/qs.git git add README.md git commit --no-verify --no-gpg-sign -m \"[readme] replace travis CI badge with shields.io check-runs badge\")",
"Bash(npm run dist:*)",
"Bash(for tag in v6.13.2 v6.12.4 v6.11.3 v6.10.6 v6.9.8 v6.8.4 v6.7.4 v6.6.2 v6.5.4 v6.4.2 v6.3.4 v6.2.5 v6.1.3 v6.0.5)",
"Bash(gh run view:*)"
]
}
}