Executive brief
qs is a widely-used Node.js library for parsing and serializing URL query strings. An attacker can craft a malicious query string that, when parsed and re-serialized by the application, causes the library to invoke a non-function as a function, throwing an uncaught exception. Applications that process untrusted user input through qs.parse() and then qs.stringify() — a common pattern in API gateways and request forwarders — can crash on a per-request basis or, in asynchronous handlers, crash the entire worker process and deny service to all requests.
Technical details
The vulnerability is a type confusion / uncaught exception flaw in qs's utils.isBuffer() function. When qs.stringify() processes each serialized value, it calls utils.isBuffer() to check if the value is a Buffer. The isBuffer() implementation performs duck-typing by invoking obj.constructor.isBuffer(obj) without first verifying that constructor.isBuffer is callable. When qs.parse() is called with plainObjects: true or allowPrototypes: true (documented mitigation options), it preserves the "constructor" key as an own property. If an attacker provides a crafted query string such as x[constructor][isBuffer]=y, the parsed object will have a non-function value at constructor.isBuffer. A subsequent qs.stringify() call on that object triggers the unchecked invocation, throwing TypeError. The attack requires no authentication and reaches the vulnerability through qs's own recommended configuration. In synchronous contexts, this causes per-request HTTP 500 errors; in async contexts without proper error handling, the exception escapes and terminates the worker process, denying service to all concurrent requests. Patch available in qs 6.16.0 and later; affected versions are 2.2.5 through 6.15.3.
Affected products
- npm qs >=2.2.5, <=6.15.3
Timeline
- 2026-09-02: disclosed: Advisory GHSA-4mjr-xmp4-gh2g published
- 2026: patched: Fixed in qs 6.16.0