Live › vulnerability › CVE-2026-110816
Go to dashboardqs.stringify with arrayFormat 'comma' throws an uncaught TypeError when an array element is an object whose own toString is not callable. An attacker who can shape data the application later serializes this way can force a synchronous throw, which crashes a process that has no error boundary around the call.
Summary written from the sources below. Check the originals before acting.
First seen by InfraStack 2026-10-27 16:15 UTC, 0h before NVD. All times on this page are UTC.
Applications that depend on qs between 6.7.0 and 6.16.0 and call stringify with the comma array format on objects that attacker input can shape. Exposure is higher when the input comes from a JSON body or a parse call with allowPrototypes or plainObjects, and when the service has no synchronous error boundary. Services using the default indices array format, or that never serialize attacker-influenced arrays with the comma format, are not affected.
| Product | Ecosystem | Affected | Fixed in |
|---|---|---|---|
| qs | npm | >= 6.7.0, <= 6.16.0 | 6.16.1 |
npm ls qs in the project and check whether the resolved version is between 6.7.0 and 6.16.0.arrayFormat: 'comma' or arrayFormat:"comma" passed to stringify, especially on data derived from request input.allowPrototypes: true or plainObjects: true is used when parsing untrusted input.qs to 6.16.1 or later.arrayFormat: 'comma' on untrusted data, or wrap qs.stringify calls in a synchronous try/catch.allowPrototypes: true or plainObjects: true where serialization follows.Each row is the first time that source carried this signal. The analyst’s rows are its own findings.
qs.stringify(obj, { arrayFormat: 'comma' }) throws an uncaught TypeError: Cannot convert object to primitive value when an array in obj contains an element that carries an own, non-callable toString property (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.
When arrayFormat: 'comma' is selected, the comma branch collapses the whole array into a single value via Array.prototype.join, or, under encodeValuesOnly: true, maps each element through the value encoder before joining. Both paths perform an implicit string coercion (ToPrimitive with hint "string") on each element. For an object whose own toString is a non-callable value, coercion skips the shadowed non-callable toString, falls through to Object.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.
lib/stringify.js (comma-array branch)
line 152-154: obj = utils.maybeMap(obj, function (v) { return v == null ? v : encoder(v); }); // encodeValuesOnly path
line 156: objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : void undefined }]; // default comma path
lib/utils.js
line 292: string = String(str); // value encoder coercion reached via the encodeValuesOnly path
Prerequisites: the application calls qs.stringify with arrayFormat: 'comma' (a documented, supported option) on an object whose shape can be influenced by attacker input. Attacker-controllable sources include qs.parse(input, { allowPrototypes: true }), which retains a toString segment (Express 4's default query parser, Express 5's extended query parser, and body-parser's extended urlencoded parser all set it), qs.parse(input, { plainObjects: true }), whose null-prototype elements throw even without a toString key (eg, a[][b]=c), and JSON.parse of 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 synchronous TypeError in 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.
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.
var qs = require('qs');
// Source A: qs.parse with allowPrototypes retains the hostile toString segment
qs.stringify(qs.parse('a[0][toString]=x', { allowPrototypes: true }), { arrayFormat: 'comma' });
// Source B: a JSON request body, no qs parse option needed
qs.stringify(JSON.parse('{"a":[{"toString":"x"}]}'), { arrayFormat: 'comma' });
// Also affected: the encodeValuesOnly sub-path
qs.stringify(JSON.parse('{"a":[{"toString":"x"}]}'), { arrayFormat: 'comma', encodeValuesOnly: true });
A default-join (allowPrototypes) => THREW: TypeError: Cannot convert object to primitive value
B default-join (JSON.parse) => THREW: TypeError: Cannot convert object to primitive value
C encodeValuesOnly path => THREW: TypeError: Cannot convert object to primitive value
N2 callable toString => NO THROW; output="a=z"
N3 isBuffer vector (CVE-2026-82417) => NO THROW; output="a%5Bconstructor%5D%5BisBuffer%5D=x"
qs version: 6.16.0
qs.stringify(obj, { arrayFormat: 'comma' }).{ a: [ { toString: <non-callable value> } ] }, either via a[0][toString]=x parsed with allowPrototypes, or via a JSON body {"a":[{"toString":"x"}]}.stringify, the primitive/buffer guard at lib/stringify.js:134 is false for an array, so control reaches the comma branch at lib/stringify.js:149.encodeValuesOnly path, the closure v == null ? v : encoder(v) at lib/stringify.js:152-154 passes only null/undefined through; a non-null object still reaches the encoder. The default path skips that closure entirely and reaches obj.join(',') at lib/stringify.js:156 with no guard at all.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 TypeError crash on null/undefined entries in the comma plus encodeValuesOnly path. Its fix (commit 21f80b3) wraps the encoder in function (v) { return v == null ? v : encoder(v); }, so it only passes null and undefined through. A non-null element with a hostile own toString fails the v == null test, so the encoder still runs and still throws; the encodeValuesOnly case (case C above) throws on the exact fixed code. The default comma path (encodeValuesOnly unset, the library default) never entered that fix at all: git log -S "obj.join(','" -- lib/stringify.js shows no security fix has ever touched the obj.join(',') line, and cases A and B throw with encodeValuesOnly unset. The related isBuffer hardening for CVE-2026-82417 (commit e83d321) is a different sink at the lib/stringify.js:134 guard, which is never entered for the array element; the negative control {a:{constructor:{isBuffer:'x'}}} serializes cleanly. Disproof attempts that failed: a -only object does not throw (it serializes to ), confirming the trigger is an element with no callable (an own non-callable , or a null-prototype object with no at all, as produces under ); nested arrays () throw the same way; a callable serializes 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.
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.0 covers every affected release. The encodeValuesOnly sub-path was added later, but the default comma-join vector already affects 6.7.0, so 6.7.0 governs the lower boundary.
The comma array
This page as .md · .json · in your agent: get_signal("CVE-2026-110816")
GitHub Advisory Database, CC-BY 4.0 · NVD data is provided by NIST and is public domain. Posts quoted belong to their authors.
Array.prototype.join, or String(str) at lib/utils.js:292). ToPrimitive skips the non-callable own toString, valueOf returns the object, and a TypeError is thrown.valueOf[object Object]toStringtoStringtoStringqs.parseplainObjects{"a":[[{"toString":"x"}]]}toString