Repository navigation
chore(deps): update dependency contentful-sdk-core>qs to v6.16.1 [security] - #180
Open
renovate[bot] wants to merge 1 commit into
Open
renovate[bot] wants to merge 1 commit into
renovate[bot] wants to merge 1 commit into
Conversation
renovate
Bot
requested review from
a team and
Tim Beyer (TimBeyer)
as code owners
October 27, 2026 20:18
renovate
Bot
force-pushed
the
renovate/npm-contentful-sdk-core-qs-vulnerability
branch
8 times, most recently
from
November 3, 2026 04:28
31877c3 to
cae721d
Compare
renovate
Bot
force-pushed
the
renovate/npm-contentful-sdk-core-qs-vulnerability
branch
3 times, most recently
from
November 4, 2026 22:45
c48c14b to
f9b92ea
Compare
renovate
Bot
force-pushed
the
renovate/npm-contentful-sdk-core-qs-vulnerability
branch
3 times, most recently
from
November 7, 2026 02:40
c723e97 to
9f53231
Compare
auto-merge was automatically disabled
November 8, 2026 11:53
Pull request was closed
renovate
Bot
deleted the
renovate/npm-contentful-sdk-core-qs-vulnerability
branch
November 8, 2026 11:53
renovate
Bot
force-pushed
the
renovate/npm-contentful-sdk-core-qs-vulnerability
branch
2 times, most recently
from
November 8, 2026 12:40
9f53231 to
70e2e57
Compare
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.1qs : 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).
Release Notes
ljharb/qs (contentful-sdk-core>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: Enabled.
♻ 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 this update again.
This PR was generated by Mend Renovate. View the repository job log.