Description
### Summary



`qs.stringify` throws a `TypeError` when it serializes an object whose own `constructor` property has a truthy, non-callable `isBuffer` member. `utils.isBuffer` duck-types buffers by calling `obj.constructor.isBuffer(obj)` after checking only that the property is truthy, so a value such as `{ constructor: { isBuffer: "x" } }` makes the call throw `TypeError: obj.constructor.isBuffer is not a function`.



### Details



`lib/stringify.js:127` calls `utils.isBuffer` on every non-primitive value it serializes. `utils.isBuffer` (`lib/utils.js:332`) reads `obj.constructor.isBuffer` and invokes it without verifying that it is a function. `constructor` and `isBuffer` are ordinary property names, so any object carrying them as own properties reaches the unchecked call.



Such an object can be built from untrusted input. `qs.parse("x[constructor][isBuffer]=y", { plainObjects: true })` or `{ allowPrototypes: true }` keeps the `constructor` key as an own property (the default parse options drop it), and `JSON.parse("{\"a\":{\"constructor\":{\"isBuffer\":\"x\"}}}")` produces the same shape with no qs option involved. Express 4 with its default `query parser` setting and body-parser with `extended: true` both call `qs.parse` with `allowPrototypes: true`, so on those stacks `req.query` and `req.body` can carry the shape directly.



#### PoC



```js



var qs = require("qs");



qs.stringify(qs.parse("x[constructor][isBuffer]=y", { plainObjects: true }));



qs.stringify(JSON.parse("{\"a\":{\"constructor\":{\"isBuffer\":\"x\"}}}"));



// TypeError: obj.constructor.isBuffer is not a function



// at Object.isBuffer (lib/utils.js:332:78)



// at stringify (lib/stringify.js:127:45)



```



#### Fix



`lib/utils.js`, applied in e83d321 on `main` and released as v6.16.0:



```diff



- return !!(obj.constructor && obj.constructor.isBuffer && obj.constructor.isBuffer(obj));



+ return !!(obj.constructor && typeof obj.constructor.isBuffer === "function" && obj.constructor.isBuffer(obj));



```



Real `Buffer`, `safer-buffer`, and browserify `buffer` polyfill instances serialize exactly as before; only the throw is removed.



### Affected versions



`>=2.2.5 <6.16.0`, fixed in v6.16.0.



The unguarded duck-type was introduced in 3768a75 and first shipped in v2.2.5 (September 2014). v2.2.4 and earlier used `Buffer.isBuffer` and are not affected. Every release from v2.2.5 through v6.15.3 contains the unguarded call.



### Impact



An unauthenticated request can make any code path that re-serializes attacker-influenced data with `qs.stringify` (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 background job, the process exits, so the impact in that case depends on the application error handling rather than on qs.
Published: 2026-08-29
Score: 6.3 Medium
EPSS: n/a
KEV: No
Impact: n/a
Action: n/a
AI Analysis

Impact

The vulnerability arises when qs.stringify calls utils.isBuffer on an object that contains a constructor property with a non-function isBuffer member. With a value like { constructor: { isBuffer: "x" } }, the call throws a TypeError, causing the serialize operation to fail. The exception propagates up the stack and, in most Node.js web frameworks, results in a 500 response or, if not caught in an async context, an unhandled promise rejection that can terminate the process. The weakness reflects improper validation of a function pointer (CWE‑248) and misuse of an unsafe function call (CWE‑703).

Affected Systems

The issue affects the npm package qs from the ljharb project. All releases from version 2.2.5 up to, but excluding, 6.16.0 contain the vulnerable code. Express 4, its default query parser, and body-parser with extended: true use qs.parse with allowPrototypes: true or plainObjects: true, allowing attacker-supplied objects to reach qs.stringify. The fix was committed in e83d321 and released in qs 6.16.0, which added a typeof check before invoking constructor.isBuffer.

Risk and Exploitability

The CVSS score is 6.3, indicating a moderate severity vulnerability. EPSS is not available, and the issue is not listed in the CISA KEV catalog. An attacker can trigger the TypeError by sending a crafted query string, JSON body, or by abusing parse options, meaning any unauthenticated client can provoke a synchronous exception or, if the exception is not properly handled, cause the process to exit. In a typical Node.js web framework, the impact is limited to a single request returning 500, but it can degrade to a full denial of service in applications that do not guard against unhandled promise rejections.

Generated by OpenCVE AI on August 30, 2026 at 01:22 UTC.

Remediation

Vendor Solution

Upgrade to qs 6.16.0 or later.


Vendor Workaround

Pass a `filter` function to `qs.stringify` that drops values carrying an own `constructor` property; it runs before the `isBuffer` check. Alternatively, wrap `qs.stringify` calls on externally influenced objects in try/catch, and avoid `allowPrototypes: true` / `plainObjects: true` when parsed untrusted input is fed back into `qs.stringify`.


OpenCVE Recommended Actions

  • Upgrade to qs 6.16.0 or later.
  • If upgrading is not possible, pass a filter function to qs.stringify to drop values with an own constructor property, or wrap calls around try/catch and avoid allowPrototypes: true / plainObjects: true when parsing untrusted input.
  • Ensure that any serialization of externally influenced data is performed inside an error boundary or after sanitization to prevent unhandled promise rejections.

Generated by OpenCVE AI on August 30, 2026 at 01:22 UTC.

Tracking

Sign in to view the affected projects.

Advisories

No advisories yet.

History

Sun, 30 Aug 2026 01:45:00 +0000

Type Values Removed Values Added
First Time appeared Ljharb
Ljharb qs
Vendors & Products Ljharb
Ljharb qs

Sun, 30 Aug 2026 00:00:00 +0000

Type Values Removed Values Added
Description ### Summary `qs.stringify` throws a `TypeError` when it serializes an object whose own `constructor` property has a truthy, non-callable `isBuffer` member. `utils.isBuffer` duck-types buffers by calling `obj.constructor.isBuffer(obj)` after checking only that the property is truthy, so a value such as `{ constructor: { isBuffer: "x" } }` makes the call throw `TypeError: obj.constructor.isBuffer is not a function`. ### Details `lib/stringify.js:127` calls `utils.isBuffer` on every non-primitive value it serializes. `utils.isBuffer` (`lib/utils.js:332`) reads `obj.constructor.isBuffer` and invokes it without verifying that it is a function. `constructor` and `isBuffer` are ordinary property names, so any object carrying them as own properties reaches the unchecked call. Such an object can be built from untrusted input. `qs.parse("x[constructor][isBuffer]=y", { plainObjects: true })` or `{ allowPrototypes: true }` keeps the `constructor` key as an own property (the default parse options drop it), and `JSON.parse("{\"a\":{\"constructor\":{\"isBuffer\":\"x\"}}}")` produces the same shape with no qs option involved. Express 4 with its default `query parser` setting and body-parser with `extended: true` both call `qs.parse` with `allowPrototypes: true`, so on those stacks `req.query` and `req.body` can carry the shape directly. #### PoC ```js var qs = require("qs"); qs.stringify(qs.parse("x[constructor][isBuffer]=y", { plainObjects: true })); qs.stringify(JSON.parse("{\"a\":{\"constructor\":{\"isBuffer\":\"x\"}}}")); // TypeError: obj.constructor.isBuffer is not a function // at Object.isBuffer (lib/utils.js:332:78) // at stringify (lib/stringify.js:127:45) ``` #### Fix `lib/utils.js`, applied in e83d321 on `main` and released as v6.16.0: ```diff - return !!(obj.constructor && obj.constructor.isBuffer && obj.constructor.isBuffer(obj)); + return !!(obj.constructor && typeof obj.constructor.isBuffer === "function" && obj.constructor.isBuffer(obj)); ``` Real `Buffer`, `safer-buffer`, and browserify `buffer` polyfill instances serialize exactly as before; only the throw is removed. ### Affected versions `>=2.2.5 <6.16.0`, fixed in v6.16.0. The unguarded duck-type was introduced in 3768a75 and first shipped in v2.2.5 (September 2014). v2.2.4 and earlier used `Buffer.isBuffer` and are not affected. Every release from v2.2.5 through v6.15.3 contains the unguarded call. ### Impact An unauthenticated request can make any code path that re-serializes attacker-influenced data with `qs.stringify` (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 background job, the process exits, so the impact in that case depends on the application error handling rather than on qs.
Title qs.stringify throws TypeError on objects with a non-callable constructor.isBuffer property
Weaknesses CWE-248
CWE-703
References
Metrics cvssV3_1

{'score': 5.3, 'vector': 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L'}

cvssV4_0

{'score': 6.3, 'vector': 'CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N'}


cve-icon MITRE

Status: PUBLISHED

Assigner: harborist

Published:

Updated: 2026-08-29T23:51:27.634Z

Reserved: 2026-08-28T23:08:00.460Z

Link: CVE-2026-82417

cve-icon Vulnrichment

No data.

cve-icon NVD

Status : Received

Published: 2026-08-30T00:16:34.657

Modified: 2026-08-30T00:16:34.657

Link: CVE-2026-82417

cve-icon Redhat

No data.

cve-icon OpenCVE Enrichment

Updated: 2026-08-30T01:30:17Z

Weaknesses
  • CWE-248

    Uncaught Exception

  • CWE-703

    Improper Check or Handling of Exceptional Conditions