Cloudanix VulnDB

CVE-2026-82417

Medium · 5.3

CVSS

5.3

EPSS

0.4%

Published

Aug 30, 2026

CISA due date

—

Cloudanix analysis

Next patch cycle SSVC: Track

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

  1. 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.
  2. Submit an unauthenticated request whose parsed object contains an own constructor.isBuffer value that is truthy but not a function.
  3. Trigger the application path that calls qs.stringify on the attacker-influenced object (redirect URL rebuild, upstream request, logging, cache key, or similar).
  4. 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.
  5. 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