CVE-2026-110816: qs.stringify with arrayFormat comma throws TypeError on array elements that cannot be coerced to a string
Summary
qs.stringify with arrayFormat: 'comma' throws TypeError: Cannot convert object to primitive value when an array element cannot be coerced to a string, such as an object with an own non-callable toString ({ "toString": "x" }) or a null-prototype object. The comma format joins array elements with Array.prototype.join and, under encodeValuesOnly, first passes each element through String() in utils.encode; neither guards against an element whose string coercion fails.
Details
In lib/stringify.js, the comma branch collapses the array with obj.join(','). For an element whose own toString is not callable, ToPrimitive skips it, falls through to Object.prototype.valueOf (which returns the object itself), and throws. Under encodeValuesOnly: true, the same element first reaches String(str) in utils.encode (lib/utils.js) and throws there. Nested arrays ({ "a": [[{ "toString": "x" }]] }) throw the same way, through Array.prototype.toString. The indices, brackets, and repeat formats walk into object elements instead of coercing them, and are not affected.
Such elements can come from untrusted input. JSON.parse produces { "a": [{ "toString": "x" }] } with no qs option involved. qs.parse("a[0][toString]=x", { allowPrototypes: true }) keeps the toString key, and qs.parse("a[][b]=c", { plainObjects: true }) produces a null-prototype element; the default parse options drop toString and produce ordinary objects, which do not throw. Express 4's default query parser, Express 5's extended query parser, and body-parser's urlencoded({ extended: true }) all call qs.parse with allowPrototypes: true.
PoC
js
var qs = require("qs");
qs.stringify(JSON.parse("{\"a\":[{\"toString\":\"x\"}]}"), { arrayFormat: "comma" });
qs.stringify(qs.parse("a[0][toString]=x", { allowPrototypes: true }), { arrayFormat: "comma" });
qs.stringify(qs.parse("a[][b]=c", { plainObjects: true }), { arrayFormat: "comma", encodeValuesOnly: true });
// TypeError: Cannot convert object to primitive value
Fix
Applied in a952e75 on main and released as v6.16.1: an element whose string coercion would fail is coerced to its primitive valueOf, if any, or else to Object.prototype.toString, so it renders as [object Object], the same as any other object element already does under the comma format. Nested arrays are walked the way join walks them. Every input that did not previously throw serializes exactly as before, and a toString or valueOf that itself throws still propagates.
Affected versions
>=6.7.0 <6.16.1, fixed in v6.16.1.
The join path was introduced with the comma format in b84acba and first shipped in v6.7.0; v6.6.x and earlier ignore arrayFormat: 'comma' and are not affected. The encodeValuesOnly String() site was introduced in 4c4b23d (v6.11.1); before that, the same input throws at join. Every release from v6.7.0 through v6.16.0 is affected.
Impact
An unauthenticated request can make any code path that re-serializes attacker-influenced data with qs.stringify and arrayFormat: 'comma' (for example, rebuilding a query string from req.query for a redirect or an upstream request, or serializing a parsed JSON body) throw synchronously. In a typical Node.js HTTP framework the throw is caught by the framework error boundary and the affected request returns a 500; the process survives and other requests are unaffected. Where the call runs outside an error boundary, such as an async Express 4 handler (where the throw becomes an unhandled promise rejection) or a bare http request handler, the process exits, so the impact in that case depends on the application error handling rather than on qs. The default arrayFormat (indices) is not affected.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
qsto a version that resolves this vulnerability.Fixed in 6.16.1
Event History
Frequently Asked Questions
Which applications are exposed to this issue?
Applications are exposed when they pass arrays containing untrusted values to qs.stringify while using arrayFormat: 'comma'. Values parsed from JSON can include the problematic object shapes, including objects with a non-callable own toString property or null-prototype objects.
What must an attacker control to trigger the failure?
An attacker needs to cause a non-coercible object to appear as an array element in data passed to qs.stringify using the comma array format. The same failure can occur in nested arrays; encodeValuesOnly: true also reaches the failing string conversion path.
What is the practical impact?
The documented impact is a TypeError that can interrupt stringification and cause availability degradation. The provided severity vector indicates no confidentiality or integrity impact.
What can be done if an update cannot be applied immediately?
Avoid arrayFormat: 'comma' for data that may contain untrusted objects. The indices, brackets, and repeat array formats walk object elements rather than coercing them and are described as unaffected.
How can I identify an affected failure in application logs?
Look for TypeError: Cannot convert object to primitive value during qs.stringify processing. This may occur while serializing arrays containing objects with an own non-callable toString property or null-prototype objects.