Repository navigation
Update dependency qs to v6.16.1 [SECURITY] - #11597
Open
backstage-goalie[bot] wants to merge 1 commit into
Open
backstage-goalie[bot] wants to merge 1 commit into
backstage-goalie[bot] wants to merge 1 commit into
Conversation
backstage-goalie
Bot
force-pushed
the
renovate/workspace-maintainer-owned/npm-qs-vulnerability
branch
3 times, most recently
from
October 15, 2026 22:24
cec8c28 to
2ab4987
Compare
backstage-goalie
Bot
deleted the
renovate/workspace-maintainer-owned/npm-qs-vulnerability
branch
October 16, 2026 12:16
backstage-goalie
Bot
force-pushed
the
renovate/workspace-maintainer-owned/npm-qs-vulnerability
branch
5 times, most recently
from
October 17, 2026 23:21
cb4646f to
539ab31
Compare
backstage-goalie
Bot
force-pushed
the
renovate/workspace-maintainer-owned/npm-qs-vulnerability
branch
4 times, most recently
from
October 19, 2026 14:24
b4eb725 to
d31e618
Compare
backstage-goalie
Bot
force-pushed
the
renovate/workspace-maintainer-owned/npm-qs-vulnerability
branch
9 times, most recently
from
October 21, 2026 19:19
feb673b to
ae32941
Compare
backstage-goalie
Bot
force-pushed
the
renovate/workspace-maintainer-owned/npm-qs-vulnerability
branch
from
October 22, 2026 01:34
ae32941 to
873ae9f
Compare
Collaborator
|
👋 Reminder: This Renovate patch/minor PR has been open for 7 days. Please review and merge if the changes look good. If no action is taken, this PR will be labeled |
backstage-goalie
Bot
force-pushed
the
renovate/workspace-maintainer-owned/npm-qs-vulnerability
branch
12 times, most recently
from
October 25, 2026 21:18
656dc14 to
f084e28
Compare
Signed-off-by: Renovate Bot <bot@renovateapp.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
6.16.0→6.16.1Warning
Some dependencies could not be looked up. Check the Dependency Dashboard for more information.
qs : qs.stringify with arrayFormat 'comma' throws an uncaught TypeError on array elements carrying a non-callable own toString (DoS)
CVE-2026-110816 / GHSA-6pq8-jhm2-2vcw
More information
Details
Summary
qs.stringify(obj, { arrayFormat: 'comma' })throws an uncaughtTypeError: Cannot convert object to primitive valuewhen an array inobjcontains an element that carries an own, non-callabletoStringproperty (for example{ a: [ { toString: 'x' } ] }). A remote, unauthenticated attacker who can influence an object that the application later re-serializes with the comma array format triggers the throw. In a service that lacks a synchronous error boundary around the serialization call, the uncaught exception crashes the worker process, producing a denial of service.Root Cause
When
arrayFormat: 'comma'is selected, the comma branch collapses the whole array into a single value viaArray.prototype.join, or, underencodeValuesOnly: true, maps each element through the value encoder before joining. Both paths perform an implicit string coercion (ToPrimitivewith hint"string") on each element. For an object whose owntoStringis a non-callable value, coercion skips the shadowed non-callabletoString, falls through toObject.prototype.valueOf(which returns the object rather than a primitive), and throws. The comma branch runs this coercion before the per-element null and skip handling in the main loop, so nothing downstream can neutralize the hostile element, and the throw is never caught inside the library.Impact
Prerequisites: the application calls
qs.stringifywitharrayFormat: 'comma'(a documented, supported option) on an object whose shape can be influenced by attacker input. Attacker-controllable sources includeqs.parse(input, { allowPrototypes: true }), which retains atoStringsegment (Express 4's default query parser, Express 5'sextendedquery parser, and body-parser'sextendedurlencoded parser all set it),qs.parse(input, { plainObjects: true }), whose null-prototype elements throw even without atoStringkey (eg,a[][b]=c), andJSON.parseof a request body, which needs no qs parse option at all. No authentication or user interaction is required. Demonstrated capability: a single request forces an uncaught synchronousTypeErrorin the serialization call. In a naive single-process Node service with no synchronous error boundary, this crashes the process and takes the service offline (availability impact). Under a framework such as Express that traps synchronous handler throws, the plausible outcome is a per-request 500 rather than a full outage; that lower-impact case is noted but not the worst credible supported deployment. There is no confidentiality or integrity impact, no memory unsafety, and no prototype pollution.Proof of Concept
Install qs at the affected version (for example 6.16.0) and run the following script with Node.js. No network setup is required; it demonstrates the throw in isolation.Attack Chain
qs.stringify(obj, { arrayFormat: 'comma' }).{ a: [ { toString: <non-callable value> } ] }, either viaa[0][toString]=xparsed withallowPrototypes, or via a JSON body{"a":[{"toString":"x"}]}.stringify, the primitive/buffer guard atlib/stringify.js:134is false for an array, so control reaches the comma branch atlib/stringify.js:149.encodeValuesOnlypath, the closurev == null ? v : encoder(v)atlib/stringify.js:152-154passes onlynull/undefinedthrough; a non-null object still reaches the encoder. The default path skips that closure entirely and reachesobj.join(',')atlib/stringify.js:156with no guard at all.Array.prototype.join, orString(str)atlib/utils.js:292).ToPrimitiveskips the non-callable owntoString,valueOfreturns the object, and aTypeErroris thrown.Bypass Evidence
This is a different element type on the code path CVE-2026-8723 (GHSA-q8mj-m7cp-5q26) patched, plus the default comma path, which that fix never covered. CVE-2026-8723 addressed a
TypeErrorcrash onnull/undefinedentries in the comma plusencodeValuesOnlypath. Its fix (commit 21f80b3) wraps the encoder infunction (v) { return v == null ? v : encoder(v); }, so it only passesnullandundefinedthrough. A non-null element with a hostile owntoStringfails thev == nulltest, so the encoder still runs and still throws; the encodeValuesOnly case (case C above) throws on the exact fixed code. The default comma path (encodeValuesOnlyunset, the library default) never entered that fix at all:git log -S "obj.join(','" -- lib/stringify.jsshows no security fix has ever touched theobj.join(',')line, and cases A and B throw withencodeValuesOnlyunset. The related isBuffer hardening for CVE-2026-82417 (commit e83d321) is a different sink at thelib/stringify.js:134guard, which is never entered for the array element; the negative control{a:{constructor:{isBuffer:'x'}}}serializes cleanly. Disproof attempts that failed: avalueOf-only object does not throw (it serializes to[object Object]), confirming the trigger is an element with no callabletoString(an own non-callabletoString, or a null-prototype object with notoStringat all, asqs.parseproduces underplainObjects); nested arrays ({"a":[[{"toString":"x"}]]}) throw the same way; a callabletoStringserializes cleanly; and the default (non-comma) array format serializes cleanly, confirming the comma format is required. All positive and negative cases were reproduced on the exact published 6.16.0 artifact.Affected Versions
Ecosystem: npmPackage: qsConfirmed affected range: >= 6.7.0, <= 6.16.0Latest release checked: 6.16.0 (npm latest; git tag v6.16.0)Fix status: fixed in 6.16.1 (a952e75)The
arrayFormat: 'comma'join sink was introduced in qs 6.7.0, so 6.7.0 is the lower boundary; releases before 6.7.0 lack the sink. No released version fixes this vector, and the latest published release (6.16.0) reproduces the throw, so the closed upper bound<= 6.16.0covers every affected release. TheencodeValuesOnlysub-path was added later, but the default comma-join vector already affects 6.7.0, so 6.7.0 governs the lower boundary.Suggested Fix
The comma array serialization must not allow an implicit string coercion of an array element to throw an uncaught exception. Coercion of each comma element should be performed defensively (for example by wrapping the join and per-element encode so that a coercion failure is caught and handled), and this protection must cover both the default
obj.join(',')path and theencodeValuesOnlyencoder path, not only the previously patchednull/undefinedcase. As an interim mitigation without a code change, applications can avoidarrayFormat: 'comma'when serializing data derived from untrusted input, sanitize parsed objects (avoidallowPrototypes/plainObjectson untrusted input and strip unexpected own properties), or wrapqs.stringifycalls in a synchronous error boundary so a coercion failure cannot crash the process.Reported by zx (GitHub: @manus-pi).
Independent report by @Lucidsec
The following was reported separately, after this advisory, and is merged here as a duplicate.
Summary
In project qs, in versions
>= 6.7.0, <= 6.16.0(every version that supportsarrayFormat: 'comma'),qs.stringifywitharrayFormat: 'comma'can throw an uncaughtTypeError: Cannot convert object to primitive valuewhen an array element has a non-callable owntoString. It is fixed in 6.16.1. Related: CWE-248 and CWE-704.Technical Details
qs.stringifywitharrayFormat: 'comma'joins an array atlib/stringify.js:156viaobj.join(','). An element whose owntoStringis not callable makesArray.prototype.jointhrow. There is no type check and no catch on that path.A second path exists with
encodeValuesOnly: atlib/stringify.js:151-154,maybeMaponly skipsv == null, thenlib/utils.js:292doesString(str)and throws the sameTypeError(this second site exists since v6.11.1; before that, the same input throws atjoin).Who can trigger: someone who can shape an array element passed to
stringify. The host must use non-defaultarrayFormat: 'comma'. DefaultarrayFormatindicesdoes not fold arrays and is not affected. A parse-then-stringify path also needsplainObjects: true(null-prototype elements fromlib/parse.js:200) orallowPrototypes: true(which Express 4's default query parser, Express 5'sextendedquery parser, and body-parser'sextendedurlencoded parser all set). Defaultqs.parse('a[0][toString]=x')returns{}because the prototype guard dropstoString. Direct JSON handed tostringifycan supply{"a":[{"toString":"x"}]}.Cause: the comma fold at
lib/stringify.js:149-156collapses the array into a single value before the main loop atlib/stringify.js:172-212, soskipNullsandstrictNullHandlingonly ever see the joined value, never individual elements.isNonNullishPrimitive/isBufferatlib/stringify.js:134inspect the whole value, not each element. A normal object usesObject.prototype.toStringand becomes[object Object]. An own non-callabletoStringis skipped,valueOfreturns the object, andjointhrows. (The skipped TODO test attest/stringify.js:684-689uses a plain{ b: 1 }element, which does not throw; it covers the separate question of what comma format should output for object elements, not this crash.) Earlier comma crash fixes did not cover object elements.Impact
One small input makes that serialization path throw a synchronous, catchable
TypeError. Behind a framework error boundary (eg, a synchronous Express handler), the request fails with a 500 and the process survives. In a host with no error boundary (eg, a barehttp.createServerhandler), the uncaught exception exits the process (the client seesECONNRESET). Availability only.Proof of Concept
Show the proof of concept
Checked on the latest commit that was checked:
07b1d4d82c8f9301c105ea4b94fa2302cfd6e8b4. Still present. Demonstrated.{"a":[{"toString":"x"}]}throws atlib/stringify.js:156.{"a":["x","y"]}returns 200 with bodya=x%2Cy.a=%5Bobject%20Object%5Dand does not throw.encodeValuesOnly: truethrows viautils.js:292.plainObjectsparse plus comma throws.Suggested Fix
Before
join, map elements with the existing primitive check; skip or replace non-primitives sojoincannot throw. On theencodeValuesOnlypath, do not send non-primitives intoString(). Optionally guardlib/utils.js:292. Add new regression tests.Severity
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
qs: Denial of Service via Attacker Controlled isBuffer
CVE-2026-82417 / GHSA-4mjr-xmp4-gh2g
More information
Details
Summary
qs.stringify()callsutils.isBuffer()on every value it serializes, andutils.isBuffer()invokesobj.constructor.isBuffer(obj)without checking that it is callable. A value whose ownconstructor.isBufferis a non-function makesqscall a non-callable and throwTypeError. Such a value is produced byqs.parseitself from an untrusted query string whenplainObjects: trueorallowPrototypes: trueis set, so a pure-qsparse→stringifyround-trip — noJSON.parse— turns an unauthenticated query string into an uncaught throw.An attacker-controlled
parseinput reaches the host application's availability asset — viaqs's own recommendedplainObjectsmitigation — and triggers an uncaught exception during aparse→stringifyround-trip.Details
utils.isBufferruns atlib/stringify.js:127for every serialized value:utils.isBuffer(lib/utils.js:327-333) invokesobj.constructor.isBufferwithout verifying it is callable:constructorandisBufferare ordinary keys.qs.parsewithplainObjects: trueorallowPrototypes: truekeeps them as own properties, so the parsed value carries a non-functionconstructor.isBuffer;stringifythen calls a non-callable and throwsTypeError. By contrastutils.isRegExpuses a brand check (Object.prototype.toString); the missing guard here is an internal inconsistency, not a platform limitation.Trust Boundary Note
qs.stringifyalone treats its input as caller-constructed, so serializing a hostile object could be argued outside its contract. This report does not depend on that framing: the malicious shape is produced byqs.parse, whose input is untrusted by design.qs.parsenormally strips aconstructorkey via its prototype guard, but with the documented optionsplainObjects: trueorallowPrototypes: truethe key survives and lands as an own property. Feeding the parsed object back intoqs.stringify— the standard round-trip in gateways and request-forwarders — then hits the unchecked call.PoC
poc02c_isBuffer_qs_only_roundtrip.js— pure-qschain, noJSON.parse; an untrusted query string alone reaches the throw:poc02_isBuffer.js— the minimal defect:poc02b_isBuffer_async_crash.js— worker death in an async sink:Execution Steps
Reproduction Evidence
poc02c_isBuffer_qs_only_roundtrip.js:poc02_isBuffer.js:poc02b_isBuffer_async_crash.js:The pure-
qsround-trip shows the malicious shape originates fromqs.parseof an untrusted query string, with noJSON.parse. The synchronoustry/catchin the async case does not catch the throw; the process exits with code 1, denying service to all requests on that worker.Impact
An unauthenticated request degrades any endpoint that re-serializes deserialized client data with
qs.stringify. The primary impact is a per-request failure: the handler throws and the framework returns HTTP 500. Where the call sits in an unguarded async continuation, the throw escapes and the worker process exits, denying service to all requests it was handling, which means a higher impact that depends on the application's error handling, not onqs.Recommended Fix
Replace the duck-type with a brand check mirroring
utils.isRegExp:If duck-typing must remain, require
typeof obj.constructor.isBuffer === 'function'before invoking and wrap the call intry/catch.Severity
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
qs array-limit bypass via bracket-key comma parsing
CVE-2026-82562 / GHSA-x5fp-wj9c-mxmx
More information
Details
Summary
qsv6.15.3allows bracket-key input to bypassarrayLimitandthrowOnLimitExceededwhencomma: true. The inputa[]=1,2,3,4succeeds witharrayLimit: 3, while the equivalent plain-key input is rejected.Affected version tested:
Details
parseArrayValue()enforces the comma limit only for flat values. Thea[]form is marked non-flat, so its comma-separated value is wrapped after parsing and the inner array is not checked. A single parameter can therefore materialize arbitrarily large arrays.PoC
On
v6.15.3, the first input parses successfully and the second creates an array with 1,000,001 elements. The equivalenta=1,2,3,4input throwsRangeErroras expected.Impact
An attacker who can supply a query string or form body can bypass configured array limits and force excessive memory allocation, causing denial of service. The limit must be applied after comma splitting and before the resulting array is wrapped.
Severity
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
Release Notes
ljharb/qs (qs)
v6.16.1Compare Source
parse: do not mutate an object inputstringify: stop re-encoding structural separator dots whenencodeDotInKeysis set (#564)stringify:arrayFormat: 'comma': pass the full encoder arguments withencodeValuesOnlyutils: encode+as%2Bin iso-8859-1 mode so values round-trip (#565)stringify: ignore inherited array format names (#622)stringify:arrayFormat: 'comma': do not throw on elements with no callabletoStringutils:merge: seed an overflow container's index from its own keys, not just the sourceutils:merge: keep astrictMergeconflict container's index andarrayLimitcorrectparse: only treat[]as array syntax when it ends the keydecode: skip decoders when there is no percent sign (#583)utils:merge: applyarrayLimitto the two-element array built for colliding valuesparse: bound the nesting added by repeatedstrictMergeconflicts (credit @Yeaseen)qsand flat parsers disagree about parameter namesthrowOnLimitExceededlimits each array, not the total number of valuescache-modeutils:decode: add missing casesparse: characterize the]=split when an=precedes any bracket group (#620)parse:allowDots: cover stray close brackets before a dot@ljharb/eslint-config,eslint,tapeConfiguration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about these updates again.
This PR has been generated by Mend Renovate CLI.