CVE-2026-110910: qs.stringify with allowEmptyArrays does not encode the keys of empty arrays, allowing query string parameter injection
Summary
qs.stringify with allowEmptyArrays: true writes the key of an empty array without encoding it, even though keys are encoded by default. A key that contains & (or the configured delimiter), =, or # ∴ becomes query string syntax instead of data, so a caller-supplied key name can add parameters to the serialized query string, or cut it short.
Details
In lib/stringify.js, the allowEmptyArrays branch returns adjustedPrefix + '[]' early, before the code that passes every other key through encoder and formatter. So { "x&k=v": ["1"] } serializes as x%26k%3Dv%5B0%5D=1, but { "x&k=v": [] } serializes as x&k=v[]. The early return applies at any depth ({ "a": { "b&k=v": [] } } gives a[b&k=v][]), under every arrayFormat, with allowDots, and with a custom encoder, which is never called for that key. With encode: false or encodeValuesOnly: true, keys are intentionally left unencoded on every path; those configurations are not affected.
PoC
js
var qs = require("qs");
var query = qs.stringify({ q: "hi", "x&apiKey=evil&y": [] }, { allowEmptyArrays: true });
// "q=hi&x&apiKey=evil&y[]"
qs.parse(query);
// { q: "hi", x: "", apiKey: "evil", y: [""] }
qs.stringify({ "x#": [], z: "1" }, { allowEmptyArrays: true });
// "x#[]&z=1": in a URL, everything after "#" becomes the fragment
Fix
Applied in 234d505 on main and released as v6.17.0: the key of an empty array goes through encoder and formatter exactly as the key of any other value does, so { "x&apiKey=evil&y": [] } now serializes as x%26apiKey%3Devil%26y[]. Keys that need no encoding serialize as before, with two exceptions that match every other path: the brackets of a nested key are now encoded (a[b][] becomes a%5Bb%5D[], which qs.parse reads identically), and with encodeDotInKeys, a dot in the key becomes %252E instead of %2E (which decodeDotInKeys previously misread as nesting).
Affected versions
>=6.12.0 <6.17.0, fixed in v6.17.0.
allowEmptyArrays was introduced in f22b2bc and first shipped in v6.12.0, with this early return. Every release from v6.12.0 through v6.16.1 is affected. Earlier versions have no allowEmptyArrays option and are not affected.
Impact
An application that passes an object whose key names come from untrusted input to qs.stringify with allowEmptyArrays: true (for example, to forward a parsed JSON body or user-selected fields to an upstream service) can have parameters injected into the resulting query string. The injected parameters are not keys of the object, so deleting or overwriting a key on the object (such as setting a trusted apiKey after copying the user's fields) does not remove them; whether the attacker's value or the application's wins depends on how the receiving side handles a duplicate parameter. A # in such a key ends the query string early when the result is placed in a URL. allowEmptyArrays is off by default, and only keys whose value (after any filter) is an empty array are 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.17.0
Event History
Frequently Asked Questions
Which applications are exposed to this issue?
Applications using npm/qs are exposed only when they call qs.stringify with allowEmptyArrays: true and allow untrusted input to control object key names. The issue can occur for empty arrays at any nesting depth.
What does an attacker need to exploit it?
An attacker needs to supply a key name containing query-string syntax such as &, the configured delimiter, =, or #, and cause that key to be serialized with an empty array value. This can inject additional query parameters or truncate the resulting query string.
Are configurations that disable key encoding affected?
No. Configurations using encode: false or encodeValuesOnly: true intentionally leave keys unencoded on all serialization paths and are not affected by this inconsistent empty-array behavior.
Does a custom encoder prevent the issue?
No. For empty arrays with allowEmptyArrays enabled, the affected early-return path does not call the custom encoder for the key.