CVE-2026-82417
Medium · 5.3CVSS
5.3
EPSS
0.4%
Published
Aug 30, 2026
CISA due date
—
Cloudanix analysis
A common web data-formatting library can crash a request or restart a server when someone sends oddly nested form or JSON fields, interrupting service until the library is updated.
- Exploitation
- Remote Unauthenticated
- Attacker needs
- None
- Complexity
- Low
- Impact
- Denial Of Service
- Component
- Library
- Class
- Denial of service
- Remediation
- Upgrade
- Effort
- Low
- Patch risk
- None
What it actually does
qs.stringify throws a synchronous TypeError when it serializes a non-primitive object whose own constructor property has a truthy, non-callable isBuffer member. utils.isBuffer duck-types Node.js Buffers by invoking obj.constructor.isBuffer(obj) after checking only that the property is truthy, not that it is a function. The throw aborts that serialization. In typical Node.js HTTP frameworks the exception is caught by the request error boundary and the client receives HTTP 500 while the process continues; if stringify runs outside a boundary (async Express 4 handlers that become unhandled promise rejections, or background jobs) the worker or process can exit. Confidentiality and integrity are not affected. Real Buffer and common buffer polyfill instances serialize normally once the function check is present (qs 6.16.0+).
Why it matters to the business
Unauthenticated clients can force individual requests to fail or, where error handling is weak, restart Node.js workers—causing availability loss, failed redirects, broken upstream calls, and noisy 500s. qs is a default dependency of Express query parsing and extended body parsing, so exposure is common across Node.js APIs. Red Hat ships affected code in Advanced Cluster Security, Ansible Automation Platform, Discovery, Grafana 13.x, Migration Toolkit for Applications, OpenShift agent-installer and migration components, and Satellite IOP. The issue is not in CISA KEV, is not tied to ransomware, and public exploit maturity is none, but a 34-day-old availability flaw in a ubiquitous serializer still warrants prompt patching because a single unauthenticated request pattern can degrade customer-facing and automation services until qs 6.16.0 or RHSA-2026:71210 is applied.
How an attack unfolds
An unauthenticated attacker supplies nested object keys so that, after parsing, an object has an own constructor property whose isBuffer value is truthy but not callable. That shape can be produced from query or form parsing when prototype-like keys are retained (plainObjects or allowPrototypes; Express 4 defaults and body-parser extended mode use allowPrototypes) or from ordinary JSON.parse of a nested object. Any later qs.stringify of that graph—rebuilding a query string for a redirect or proxy, serializing a parsed body, or similar—hits the unguarded duck-type and throws TypeError. The attacker repeats the request to sustain 500 responses or to crash workers that lack an error boundary. No credentials or user interaction are required. Impact is limited to availability; there is no known named threat-actor use and exploit maturity is none.
How to fix it
Upgrade the qs package to 6.16.0 or later (unguarded duck-type existed from 2.2.5 through 6.15.3; 2.2.4 and earlier used Buffer.isBuffer and are not affected). On Red Hat products apply RHSA-2026:71210: advanced-cluster-security/rhacs main-rhel8:1789411269; ansible-automation-platform 26/lightspeed-rhel9:1789656884; discovery/discovery ui-rhel9:1789677459; grafana13-1-main 13.1.3-0.4.hum1; grafana13-2-main 13.2.1-0.1.hum1; mta/mta ui-rhel9:1789446780; openshift4/ose-agent-installer ui-rhel9:1789546161; rhmtc/openshift-migration ui-rhel8:1789546373; satellite/iop remediations-rhel9:1789615593. Rebuild and redeploy images so lockfiles and node_modules no longer pin qs < 6.16.0. Compensating controls until upgrade: wrap qs.stringify of untrusted objects in try/catch; avoid re-serializing req.query/req.body; prefer not enabling allowPrototypes/plainObjects unless required; run stringify only inside framework error boundaries so a throw cannot become an unhandled rejection.
How to confirm the fix worked
Inventory qs with npm ls qs, yarn why qs, or equivalent lockfile review and confirm every copy is >= 6.16.0 (or the RHSA-fixed image digest/tag above). After upgrade, serialization of objects that previously tripped the duck-type must complete without TypeError, while real Buffer instances still stringify as before. Confirm Express query parser and body-parser extended settings, and grep application code for qs.stringify on request-derived objects. In staging, send benign traffic plus previously crashing nested constructor/isBuffer shapes and verify no 500 and no process exit. Regression-test redirects and upstream query rebuilds.
Typical attack path
- Locate a Node.js endpoint that parses untrusted query, form, or JSON input with qs (or Express / body-parser defaults) and later re-serializes that object with qs.stringify.
- Submit an unauthenticated request whose parsed object contains an own constructor.isBuffer value that is truthy but not a function.
- Trigger the application path that calls qs.stringify on the attacker-influenced object (redirect URL rebuild, upstream request, logging, cache key, or similar).
- Observe a synchronous TypeError mapped to HTTP 500, or a worker/process exit if the throw is an unhandled rejection or runs in a job outside an error boundary.
- Repeat the request to sustain denial of service against the affected route or process pool.
How to detect it
This is a request-triggered availability issue, not a persistence implant; look for whether the throw already occurred rather than for malware artifacts. Search application and Node.js stderr/stdout, container logs, and APM error trackers for TypeError stating that constructor.isBuffer is not a function, with stack frames in qs lib/utils.js isBuffer and lib/stringify.js. Correlate bursts of HTTP 500 on unauthenticated GET/POST routes that rebuild query strings or re-serialize bodies, plus unexpected worker restarts or unhandled promise rejections around stringify. Inspect access logs and WAF/body captures for nested constructor and isBuffer keys in query strings, application/x-www-form-urlencoded bodies, or JSON objects. No named APT or ransomware campaign is associated; exploit maturity is none, so volume 500s and matching stack traces are the primary evidence. Retain core dumps or exit logs if a process died outside an error boundary.
- TypeError: obj.constructor.isBuffer is not a function
- Object.isBuffer (lib/utils.js
- at stringify (lib/stringify.js
- constructor][isBuffer]
- [constructor][isBuffer]=
- "constructor":{"isBuffer"
- unhandledRejection
- qs.stringify
What your security team should do
- Patch qs to 6.16.0+ and apply RHSA-2026:71210 to all listed Red Hat images; treat lockfile/transitive copies as in-scope.
- Prioritize services that stringify req.query, req.body, or parsed JSON (redirects, proxies, webhooks, jobs) and confirm those paths sit inside an error boundary.
- Hunt historical 500s and process restarts for the TypeError and qs stringify/isBuffer stack frames; spike correlation on unauthenticated routes.
- Until patched, catch stringify failures, rate-limit noisy 500 routes, and reduce allowPrototypes/plainObjects where the app does not need prototype keys.
- Add SCA/allowlist policy so qs cannot regress below 6.16.0 in CI.
The record
### 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.
Affected packages
| Ecosystem | Package | Introduced | Fixed |
|---|
Related vulnerabilities
- CVE-2026-100347 kin-openapi is a Go project for handling OpenAPI files. CWE-248
- CVE-2026-102599 Socket.IO enables bidirectional and low-latency communication for every platform. CWE-248
- CVE-2026-107382 MariaDB Connector/Node.js is used to connect applications developed on Node.js to MariaDB… CWE-248
- CVE-2026-106122 The RabbitMQ Java client library allows Java and JVM-based applications to connect to and… CWE-248
- CVE-2026-105757 vLLM is an inference and serving engine for large language models. CWE-248
- CVE-2026-105756 vLLM is an inference and serving engine for large language models. CWE-248
- CVE-2026-103387 A weakness has been identified in garycourt uri-js up to 4.4.1. CWE-248
- CVE-2026-102281 Nest is a framework for building scalable Node.js server-side applications. CWE-248