Hi,
The following vulnerability was published for node-qs.
CVE-2026-110816[0]:
| ### 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.
https://github.com/ljharb/qs/security/advisories/GHSA-6pq8-jhm2-2vcw
https://github.com/ljharb/qs/commit/a952e7519338ef0cfb25694a2988848f93333727 (v6.16.1)
If you fix the vulnerability please also make sure to include the
CVE (Common Vulnerabilities & Exposures) id in your changelog entry.
For further information see:
[0] https://security-tracker.debian.org/tracker/CVE-2026-110816
https://www.cve.org/CVERecord?id=CVE-2026-110816
Please adjust the affected versions in the BTS as needed.