+5
-0
@@ -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 @@ |
+1
-1
@@ -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 @@ |
+1
-1
@@ -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 @@ |
+12
-3
@@ -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" | ||
| ] | ||
| } | ||
| } |
+9
-0
@@ -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:*)" | ||
| ] | ||
| } | ||
| } |
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
0
-100%171535
-4.91%18
5.88%17
-22.73%2931
-0.78%