Repository navigation
Security patches (release/2.2.0) - autoclosed - #439
Closed
renovate[bot] wants to merge 1 commit into
Closed
renovate[bot] wants to merge 1 commit into
renovate[bot] wants to merge 1 commit into
Conversation
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.11.6.3→1.6.11qs : 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).
Seroval: Deserialization invokes attacker-chosen callables: AsyncIteratorFactoryInstance calls source.on(listener) without type check
CVE-2026-116580 / GHSA-7rv5-mrxv-xcr9
More information
Details
Summary
When
fromJSONrebuilt an async iterator, it calledon()on the iterator's source without checking that the source was a seroval stream. A crafted payload could pick another value as the source, such as a function returned by an application plugin. The deserializer then called that value'sonmethod.This is a variant of GHSA-mv8w-475r-vwqw. That fix covered the promise and stream control nodes but not the async iterator node.
Impact
An application that passes untrusted input to
fromJSONwith plugins that return callable values can have those callables invoked during deserialization. What this allows depends on what the plugin's values do.Affected versions
Patches
Fixed in
seroval@1.6.8. Streams and sequences are now internal class instances. The deserializer checks the source value withinstanceof, so plain data and plugin values are rejected no matter which node id they use.Workarounds
Avoid plugins whose
deserializereturns functions or objects with anonmethod when decoding untrusted input. Upgrading to 1.6.8 is recommended.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:LReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
Seroval: fromJSON deserializer trusts node-supplied array length and sequence doneAt (unauthenticated event-loop DoS)
CVE-2026-116581 / GHSA-97r2-qh9m-474h
More information
Details
Summary
fromJSONandfromCrossJSONused two node fields without checking them.Impact
A service that passes untrusted input to
fromJSONorfromCrossJSONcan be made unavailable by an unauthenticated request.Patches
Fixed in
seroval@1.6.8.Invalid input throws
SerovalMalformedNodeError.Workarounds
None. Upgrade to 1.6.8.
Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:HReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
Seroval
fromJSON/fromCrossJSONdenial of service via unvalidated array node lengthCVE-2026-116584 / GHSA-wp97-x4hv-qvhp
More information
Details
Summary
fromJSONandfromCrossJSONdid not check that an array node's element list is an array. A small crafted payload could set the length the decoder used. That length controlled how much memory was allocated and how many times the decoder looped, and it did not depend on the size of the payload.Impact
A service that passes untrusted input to
fromJSONorfromCrossJSONcan be made unavailable by an unauthenticated request.This affects any framework that decodes request bodies with these functions before its own validation runs.
This issue is a bypass of the fix for GHSA-66fc-rw6m-c2q6 in 1.4.1.
Patches
Fixed in
seroval@1.6.8. The deserializer now rejects any node whose element list is not an array, and throwsSerovalMalformedNodeError.Workarounds
None. Upgrade to 1.6.8.
Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:HReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
Seroval: Script injection through RegExp sources in serialized output
CVE-2026-116582 / GHSA-9wx9-5pv9-3349
More information
Details
Summary
seroval writes a
RegExpas a regular expression literal. In affected versions, the<character in aRegExpsource is written without escaping. A source built from untrusted input can then end the HTML<script>element that the serialized output is placed in, and run script in the page.Details
seroval escapes
<in strings, so string values cannot end a<script>element.RegExpsources did not get the same escaping:<inRegExpsources, so that named groups and lookbehinds keep working. This removed the escaping for every<in a source.These functions are affected:
serialize,serializeAsynccrossSerialize,crossSerializeAsync,crossSerializeStreamSerializerclasscompileJSONtoJSON,fromJSON,toCrossJSONandfromCrossJSONare not affected, because they never produce JavaScript.Impact
An application is affected when all of these are true:
RegExpfrom untrusted input that is used as regular expression syntax. Input passed through an escaping helper (such as anescapeRegExpfunction) is not enough to exploit this.RegExpwith one of the functions above.<script>element, as server rendering usually does.In that case an attacker can run script in the page of any user who receives that output.
Patches
Fixed in 1.6.10. A
RegExpwhose source contains<is now written with theRegExpconstructor and an escaped string source. Named groups and lookbehinds keep working.Workarounds
If you cannot upgrade:
RegExpbuilt from untrusted pattern syntax. Send its source and flags as strings, and build theRegExpon the client.<before they reach the serializer.Severity
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
Seroval: Code injection through a typed array's own constructor property
CVE-2026-116583 / GHSA-f86p-h4j2-7qgj
More information
Details
Summary
seroval writes a typed array as a call to its constructor. In affected versions, the constructor name in the output is read from a property that data on the value can replace, and it is written without checks. A typed array that received properties from untrusted data can then put arbitrary code into the serialized output.
Details
Version 1.6.0 changed how seroval finds the class of an object, so that objects with their own
constructorproperty serialize correctly. Typed arrays still took the constructor name from the value itself, so the name could come from the untrusted property instead of the array's real type.These functions are affected:
serialize,serializeAsynccrossSerialize,crossSerializeAsync,crossSerializeStreamSerializerclasscompileJSONfromJSONandfromCrossJSONare not affected: they only accept the names of the built-in typed arrays. With thecompactArrayBufferViewsoption, the same lookup throws aTypeErrorinstead of writing code.Impact
An application is affected only when it serializes a typed array that has an own
constructorproperty set from untrusted data. That requires code that copies properties from untrusted objects onto a typed array. This is unusual, so the severity is rated Low, even though the result is script execution in the page that receives the output.Patches
Fixed in 1.6.11. The constructor name now comes from the array's internal type, which properties on the value cannot change.
compactArrayBufferViewsuses the real type as well.Workarounds
If you cannot upgrade, do not copy properties from untrusted objects onto typed arrays, or remove an own
constructorproperty from a typed array before serializing it.Severity
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:P/VC:N/VI:N/VA:N/SC:H/SI:H/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,tapelxsmnsyc/seroval (seroval)
v1.6.11Compare Source
v1.6.10Compare Source
v1.6.9Compare Source
v1.6.8Compare Source
Configuration
📅 Schedule: (in timezone Europe/Warsaw)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
👻 Immortal: This PR will be recreated if closed unmerged. Get config help if that's undesired.
This PR was generated by Mend Renovate. View the repository job log.