CVE-2026-110816
Medium · 5.3CVSS
5.3
EPSS
0.4%
Published
Oct 14, 2026
CISA due date
—
Cloudanix analysis
Certain unusual data sent to a web application can cause it to crash when the software tries to rebuild a request string, disrupting service until the library is updated.
- Exploitation
- Remote Unauthenticated
- Attacker needs
- None
- Complexity
- Low
- Impact
- Denial Of Service
- Component
- Library
- Class
- Improper Input Validation
- Remediation
- Upgrade
- Effort
- Low
- Patch risk
- None
What it actually does
qs.stringify with arrayFormat: 'comma' (and encodeValuesOnly) throws TypeError: Cannot convert object to primitive value on array elements that fail string coercion, such as objects with non-callable toString or null-prototype objects, because join and String() calls lack guards; other arrayFormat options are unaffected.
Why it matters to the business
Unauthenticated requests can trigger synchronous crashes or 500 responses in applications that re-serialize parsed data using the vulnerable qs path, leading to denial of service; recent publication and required upgrades in Grafana 13.1/13.2 packages increase urgency for affected Red Hat deployments.
How an attack unfolds
An attacker supplies input (via JSON.parse, qs.parse with allowPrototypes:true or plainObjects:true) that produces arrays containing uncoercible objects; when the application later calls qs.stringify on that data with arrayFormat:'comma', the library throws, terminating the request or process depending on surrounding error handling.
How to fix it
Upgrade qs to 6.16.1 or later; for the listed Grafana packages apply the exact fixed versions grafana13-1-main (redhat) 13.1.8-0.3.hum1 and grafana13-2-main (redhat) 13.2.3-0.3.hum1 per RHSA-2026:87077; wrap stringify calls in try/catch and avoid arrayFormat:comma on untrusted data until patched.
How to confirm the fix worked
Re-run the published PoC inputs after upgrade and confirm they no longer throw; verify the library version in package.json/lockfile and test that existing comma-format serialization of ordinary arrays continues to produce identical output.
Typical attack path
- Supply crafted query string or JSON body that qs.parse converts into an array containing an uncoercible object
- Trigger application code path that rebuilds a query string or serializes the data using qs.stringify with arrayFormat set to comma
- Force the join/String coercion that throws TypeError, resulting in 500 response or unhandled rejection
How to detect it
Search application and web-server logs for the exact TypeError message in request-handling threads; correlate with recent qs.parse calls using allowPrototypes or plainObjects options and subsequent stringify operations.
- TypeError: Cannot convert object to primitive value
- arrayFormat.*comma
What your security team should do
- Immediately upgrade qs to 6.16.1 and apply the listed Grafana Red Hat patches
- Audit all call sites that invoke qs.stringify with arrayFormat:comma
- Add defensive error handling around serialization of request-derived data
- Monitor for the TypeError in application logs as a temporary detection measure
The record
### 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 packages
| Ecosystem | Package | Introduced | Fixed |
|---|
Related vulnerabilities
- CVE-2026-63341 Aedes is an MQTT broker that can run on any stream server. CWE-248
- CVE-2026-119188 Forge (also called node-forge) is a native implementation of Transport Layer Security in… CWE-248
- CVE-2026-119052 llama.cpp b2877 before b11935 contains an uncaught exception vulnerability in the… CWE-248
- CVE-2026-111107 A NULL pointer dereference vulnerability in WatchGuard Fireware OS’s management interface… CWE-248
- CVE-2026-117541 svg-sanitizer from 0.11.0 before 1.1.0 contains an uncaught exception vulnerability that… CWE-248
- CVE-2026-116589 Datadog dd-trace-py is the Datadog Python APM client. CWE-248
- CVE-2026-115892 Wasmtime is a runtime for WebAssembly. CWE-248
- CVE-2026-89318 `JweRsaKeyEncryptionDecryptorImpl.Decrypt` passed the JWE's initialization vector to… CWE-248