CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-110816

CVE-2026-110816: Denial of Service via Uncaught Exception in Node.js qs Library

Alon Barad
Alon Barad
Software Engineer

Oct 27, 2026·10 min read·4 visits

Executive Summary (TL;DR)

Calling qs.stringify with arrayFormat: 'comma' on arrays containing objects without a callable toString method or null-prototype objects throws an unhandled TypeError: Cannot convert object to primitive value. In Node.js applications that re-serialize untrusted user input, this causes request failures or full process crashes. Upgrading qs to version 6.16.1 resolves the issue.

CVE-2026-110816 (GHSA-6pq8-jhm2-2vcw) is an uncaught exception and improper type conversion vulnerability in the Node.js query string parsing and serialization library qs. When invoking qs.stringify with the option arrayFormat set to 'comma', the library fails to safely handle array elements that cannot be coerced into primitive strings, such as objects with a non-callable toString property or null-prototype objects. An unauthenticated remote attacker can supply crafted input via JSON bodies or URL query parameters that, when re-serialized by the application, throws a synchronous TypeError, resulting in HTTP 500 errors or complete Node.js process termination.

Vulnerability Overview

The qs package is a widely deployed query string parsing and serialization library in the Node.js ecosystem. It serves as the foundational query string engine for major web frameworks, including Express 4, Express 5, and the body-parser middleware. Applications frequently use qs.parse to deserialize incoming HTTP query parameters or URL-encoded bodies, and qs.stringify to serialize JavaScript objects back into query strings for upstream API requests, redirects, caching layers, or logging.

CVE-2026-110816 (also tracked as GHSA-6pq8-jhm2-2vcw) is a Denial of Service vulnerability categorized under CWE-248 (Uncaught Exception) and CWE-704 (Incorrect Type Conversion or Cast). The flaw resides in the qs.stringify method when configured with the arrayFormat: 'comma' option. When serializing an array under this format, qs attempts to coerce array elements into primitive strings without verifying that the elements support standard ECMAScript string conversion.

If an attacker injects an object into an array that possesses a non-callable toString property (such as { "toString": "x" }) or is a null-prototype object (Object.create(null)), the underlying JavaScript engine throws a synchronous TypeError: Cannot convert object to primitive value. This vulnerability affects all versions of qs from 6.7.0 through 6.16.0.

Depending on the host application's error handling architecture, this uncaught synchronous exception either terminates the active HTTP request with a 500 Internal Server Error or crashes the entire Node.js runtime process. Applications using the default arrayFormat (indices), or the brackets and repeat formats, are unaffected because those code paths recursively traverse object properties rather than coercing the object directly to a primitive string.

Root Cause Analysis

To understand the root cause of CVE-2026-110816, it is necessary to examine how the V8 JavaScript engine executes the ECMAScript ToPrimitive(input, PreferredType: string) abstract operation. When an object is passed to String(obj) or joined via Array.prototype.join(','), the engine first checks for a callable [Symbol.toPrimitive] method. If absent, it looks up the object's toString property. If obj.toString is a callable function, the engine invokes it and returns the resulting primitive string.

However, if obj.toString is overridden with a non-callable value (for example, a string literal "x"), the engine skips toString and falls back to invoking obj.valueOf(). For standard JavaScript objects, Object.prototype.valueOf() returns a reference to the object itself—which is not a primitive value. Because neither method yields a primitive, the ECMAScript specification mandates throwing a runtime TypeError: Cannot convert object to primitive value. The exact same failure occurs on null-prototype objects (Object.create(null)) because neither toString nor valueOf exists on their prototype chain.

In qs versions 6.7.0 (introduced in commit b84acba) through 6.16.0, the arrayFormat: 'comma' execution branch in lib/stringify.js directly invokes obj.join(',') on the target array. Furthermore, starting in version 6.11.1 (commit 4c4b23d), when the encodeValuesOnly: true option is enabled, qs maps over the array elements and passes each item to utils.encode in lib/utils.js prior to joining. Inside utils.encode, any non-string, non-symbol value is coerced via String(str).

Neither obj.join(',') in lib/stringify.js nor String(str) in lib/utils.js guards against objects whose ToPrimitive coercion fails. Furthermore, because Array.prototype.join recursively converts nested arrays via Array.prototype.toString, nested structures such as { "a": [[{ "toString": "x" }]] } trigger the exact same exception path.

Code Analysis

In the vulnerable versions of qs, lib/stringify.js handles the comma array format by mapping elements through an encoder (if encodeValuesOnly is true) and immediately joining the array elements with a comma delimiter. In lib/utils.js, the encode function forces non-string primitives and objects through the global String() constructor.

The official remediation in version 6.16.1 (commit a952e7519338ef0cfb25694a2988848f93333727) introduces a defensive helper function named toCoercible inside lib/utils.js. Both lib/stringify.js and lib/utils.js are updated to sanitize inputs through toCoercible before invoking .join(',') or String().

// lib/stringify.js - Patched comma array handling
if (arrayFormat === 'comma' && isArray(obj)) {
    if (encodeValuesOnly && typeof encoder === 'function') {
        obj = utils.maybeMap(obj, function (v) {
            return v == null ? v : encoder(v);
        });
    }
    // FIX: Wrap `obj` in `utils.toCoercible(obj)` before calling `.join(',')`
    objKeys = [{ value: obj.length > 0 ? utils.toCoercible(obj).join(',') || null : void undefined }];
}

Inside lib/utils.js, the toCoercible function inspects values before native coercion occurs. Primitives and objects implementing [Symbol.toPrimitive] or a valid callable toString method are returned unmodified. If toString is not a function, toCoercible attempts to call valueOf() directly; if valueOf() returns a primitive, it converts that primitive to a string. Otherwise, it falls back to Object.prototype.toString.call(value), which safely evaluates to "[object Object]".

// lib/utils.js - New toCoercible sanitization logic
var hasToPrimitive = typeof Symbol === 'function' && typeof Symbol.toPrimitive === 'symbol';
var arrayJoin = Array.prototype.join;
var arrayToString = Array.prototype.toString;
var objectToString = Object.prototype.toString;
 
var toCoercible = function toCoercible(value, joining) {
    // 1. Primitives and null/undefined do not throw on coercion
    if (!value || (typeof value !== 'object' && typeof value !== 'function')) {
        return value;
    }
    // 2. Respect custom Symbol.toPrimitive implementations
    if (hasToPrimitive && value[Symbol.toPrimitive] != null) {
        return value;
    }
    // 3. Recursively walk nested arrays while tracking cycles via side-channel
    if (isArray(value) && value.toString === arrayToString && value.join === arrayJoin) {
        var ancestors = joining || getSideChannel();
        if (ancestors.has(value)) {
            return ''; // Render cyclical array references as empty string
        }
        ancestors.set(value, true);
        var copy;
        for (var i = 0; i < value.length; i += 1) {
            var item = value[i];
            var coerced = toCoercible(item, ancestors);
            // Lazy copy-on-write only if an element required transformation
            if (!copy && coerced !== item) {
                copy = [];
                for (var j = 0; j < i; j += 1) {
                    copy[j] = value[j];
                }
            }
            if (copy) {
                copy[i] = coerced;
            }
        }
        ancestors['delete'](value);
        return copy || value;
    }
    // 4. If toString is a valid function, native coercion will succeed
    if (typeof value.toString === 'function') {
        return value;
    }
    // 5. Fallback: try valueOf() if callable, otherwise force Object.prototype.toString
    var valueOf = value.valueOf;
    if (typeof valueOf === 'function') {
        var primitive = valueOf.call(value);
        if (!primitive || (typeof primitive !== 'object' && typeof primitive !== 'function')) {
            return String(primitive);
        }
    }
    return objectToString.call(value);
};

The patch is comprehensive and addresses several subtle edge cases. By implementing recursive array traversal with a getSideChannel() ancestor map, toCoercible prevents infinite recursion on circular array references, matching native Array.prototype.join cycle behavior by returning ''. Additionally, it uses a copy-on-write pattern so arrays containing benign elements incur zero memory allocation overhead. Note that if an application intentionally passes an object with a callable toString or valueOf function that explicitly throws an exception inside its body, that exception will still propagate as expected.

Exploitation

Exploiting CVE-2026-110816 requires no authentication and relies on supplying structured input that deserializes into an array containing an uncoercible object, which the target application subsequently passes to qs.stringify(data, { arrayFormat: 'comma' }). While qs.parse drops prototype properties like toString under its default standalone options, the most common web framework integrations override this behavior by default.

Specifically, Express 4's default query parser, Express 5's extended query parser, and body-parser's urlencoded({ extended: true }) middleware all invoke qs.parse with { allowPrototypes: true }. When allowPrototypes: true is active, an attacker can send a URL query string or form body such as a[0][toString]=x. qs.parse constructs the array [{ toString: 'x' }], shadowing Object.prototype.toString with an own string property.

const qs = require('qs');
 
// Vector 1: Express 4 / Express 5 extended query parser configuration (allowPrototypes: true)
// Attacker sends HTTP GET: /endpoint?a[0][toString]=x
const parsedQuery = qs.parse('a[0][toString]=x', { allowPrototypes: true });
try {
    qs.stringify(parsedQuery, { arrayFormat: 'comma' });
} catch (err) {
    // Throws TypeError: Cannot convert object to primitive value
    console.error(err);
}
 
// Vector 2: Standard JSON body parsing (no special qs.parse options required)
// Attacker sends HTTP POST with Content-Type: application/json
const parsedJson = JSON.parse('{"a":[{"toString":"x"}]}');
try {
    qs.stringify(parsedJson, { arrayFormat: 'comma' });
} catch (err) {
    // Throws TypeError: Cannot convert object to primitive value
    console.error(err);
}
 
// Vector 3: Null-prototype objects generated via plainObjects: true
// Attacker sends HTTP GET: /endpoint?a[][b]=c
const plainParsed = qs.parse('a[][b]=c', { plainObjects: true });
try {
    qs.stringify(plainParsed, { arrayFormat: 'comma', encodeValuesOnly: true });
} catch (err) {
    // Throws TypeError: Cannot convert object to primitive value inside utils.encode
    console.error(err);
}

As shown in the reproduction vectors above, an attacker does not even need allowPrototypes: true or plainObjects: true if the endpoint accepts JSON payloads. Standard JSON.parse('{"a":[{"toString":"x"}]}') natively assigns toString as an own property on the resulting object. When the server forwards or normalizes this payload using qs.stringify with arrayFormat: 'comma', the exception triggers immediately.

Impact Assessment

CVE-2026-110816 carries a CVSS v3.1 base score of 5.3 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L) and a CVSS v4.0 vector of 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. The vulnerability does not allow confidentiality breaches or memory corruption, as the impact is strictly confined to service availability.

The operational severity of the availability impact depends heavily on the execution context within the Node.js application. When qs.stringify is called synchronously inside a standard Express route handler, the framework's built-in error boundary catches the TypeError and responds to the client with an HTTP 500 error. In this scenario, the Node.js process survives and continues serving other clients, limiting the impact to per-request failures.

> [!WARNING] > In Express 4 applications, unhandled synchronous exceptions thrown inside async route handlers or unawaited Promise chains are not caught by the default router error boundary. This results in an unhandledRejection event, which terminates the entire Node.js process in modern Node.js runtimes.

Similarly, if qs.stringify is invoked inside a bare http.createServer request listener, an asynchronous timer callback (setTimeout / setImmediate), or background worker thread without an explicit try...catch block, the uncaught exception immediately crashes the Node.js process. An unauthenticated attacker can repeatedly send a single lightweight HTTP request to keep the application offline in a continuous crash loop.

Threat intelligence metrics currently indicate an EPSS score of 0.00368 (30.52nd percentile), and the vulnerability is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog.

Remediation

The primary remediation is to upgrade the qs dependency to version 6.16.1 or later. Version 6.16.1 incorporates the toCoercible check in lib/utils.js and lib/stringify.js, ensuring that objects lacking a callable toString method or prototype chain are safely serialized as "[object Object]" without throwing runtime exceptions.

# Upgrade qs to the patched release
npm install qs@^6.16.1

If qs is pulled in as a transitive dependency through frameworks such as Express or body-parser, administrators should use npm ls qs to inspect the dependency tree and apply an overrides entry in package.json (or resolutions in Yarn) to force resolution to >=6.16.1.

For environments where immediate package upgrades are not feasible, development teams can apply several workarounds:

  1. Change the Array Format: Avoid using arrayFormat: 'comma'. Switching to the default indices format (a[0]=1&a[1]=2), brackets (a[]=1&a[]=2), or repeat (a=1&a=2) bypasses the vulnerable array joining code path entirely.
  2. Implement a Custom Filter: Pass a filter function to qs.stringify to inspect and sanitize non-coercible objects prior to serialization:
const safeStringify = (payload) => {
    return qs.stringify(payload, {
        arrayFormat: 'comma',
        filter: (prefix, value) => {
            if (typeof value === 'object' && value !== null && typeof value.toString !== 'function') {
                return Object.prototype.toString.call(value);
            }
            return value;
        }
    });
};
  1. Harden Parser and Error Boundaries: Disable allowPrototypes in query parsers where prototype property access is not explicitly required, and ensure all qs.stringify calls inside asynchronous handlers are wrapped in try...catch blocks.

Official Patches

ljharbCommit fixing uncaught TypeError in qs.stringify with arrayFormat: 'comma'
ljharbOfficial v6.16.1 release of qs
GitHubGitHub Security Advisory GHSA-6pq8-jhm2-2vcw

Fix Analysis (1)

Technical Appendix

CVSS Score
5.3/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
EPSS Probability
0.37%
Top 69% most exploited

Affected Systems

Node.js applications using qs >= 6.7.0 and < 6.16.1Express 4 and Express 5 applications re-serializing query or body parameters with arrayFormat: 'comma'

Affected Versions Detail

Product
Affected Versions
Fixed Version
qs
ljharb
>= 6.7.0 < 6.16.16.16.1
AttributeDetail
Vulnerability TypeCWE-248 (Uncaught Exception) / CWE-704 (Incorrect Type Conversion)
Affected Componentqs (npm package) >= 6.7.0, < 6.16.1
Attack VectorNetwork (Unauthenticated HTTP requests with crafted query or JSON parameters)
CVSS v3.1 Score5.3 Medium (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L)
EPSS Score0.37% (30.52nd percentile)
Security ImpactDenial of Service (HTTP 500 errors or Node.js process crash)
Exploit StatusProof-of-Concept publicly available in advisory
CISA KEV StatusNot Listed

MITRE ATT&CK Mapping

T1499Endpoint Denial of Service
Impact
T1499.004Application or System Exploitation
Impact
CWE-248
Uncaught Exception

The software does not catch or properly handle an exception that can occur during normal or exceptional operation, which may lead to a denial of service or unexpected program termination.

Vulnerability Timeline

Related limit documentation and threat model clarifications committed to ljharb/qs (07b1d4d82c8f9301c105ea4b94fa2302cfd6e8b4)
2026-09-12
Remediation patch committed to ljharb/qs repository (a952e7519338ef0cfb25694a2988848f93333727) and released in v6.16.1
2026-10-14
GitHub Security Advisory GHSA-6pq8-jhm2-2vcw and CVE-2026-110816 officially published
2026-10-14
CVE-2026-110816 record updated on CVE.org and NVD
2026-10-16

References & Sources

  • [1]CVE-2026-110816 Official CVE.org Record
  • [2]National Vulnerability Database (NVD) - CVE-2026-110816
  • [3]GitHub Security Advisory GHSA-6pq8-jhm2-2vcw
  • [4]Patch Commit a952e7519338ef0cfb25694a2988848f93333727 in ljharb/qs
  • [5]qs v6.16.1 Release Notes
  • [6]ljharb/qs GitHub Repository
  • [7]qs Package on npm
  • [8]CVEProject cvelistV5 JSON Record for CVE-2026-110816
  • [9]Red Hat Customer Portal - CVE-2026-110816
  • [10]Red Hat Bugzilla #2552954
  • [11]Wiz Vulnerability Database - CVE-2026-110816

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•11 minutes ago•GHSA-WF6J-7HVR-GR93
7.8

GHSA-wf6j-7hvr-gr93: Arbitrary Code Execution via PyYAML Target Blocklist Bypass in hydra-core

A high-severity target blocklist omission in the hydra-core Python package (versions 1.3.6 through 1.3.7 and 1.4.0.dev9 through 1.4.0rc2) allows arbitrary code execution when instantiating untrusted configuration dictionaries. Because the execution policy failed to restrict the PyYAML (yaml.) namespace, an attacker can specify functions such as yaml.unsafe_load as the instantiation target and supply a crafted YAML stream containing Python object construction tags to execute arbitrary system commands within the host process.

Alon Barad
Alon Barad
0 views•10 min read
•about 2 hours ago•CVE-2026-114620
5.4

CVE-2026-114620: Server-Side Request Forgery in Authlib RFC 9101 JWT-Secured Authorization Request

Authlib versions prior to 1.9.0 contain a Server-Side Request Forgery (SSRF) vulnerability in the RFC 9101 JWT-Secured Authorization Request (JAR) server implementation. The JWTAuthenticationRequest class passes an unauthenticated, client-controlled request_uri parameter directly to the abstract get_request_object() method before applying any destination validation or client registration policies. Because the official documentation previously recommended implementing this hook as an unrestricted HTTP fetch, deployments following those guidelines allow remote attackers possessing a valid public client_id to trigger outbound network requests to internal services and infer fetch outcomes via error response variations.

Amit Schendel
Amit Schendel
3 views•11 min read
•about 3 hours ago•GHSA-J456-XH4H-CPF2
7.0

CVE-2026-109956: Path Traversal and Sandbox Bypass in NLTK WekaClassifier

CVE-2026-109956 (GHSA-j456-xh4h-cpf2) is a path traversal vulnerability and security sandbox bypass in the Python Natural Language Toolkit (NLTK) versions 3.10.3 and earlier. The WekaClassifier module fails to validate caller-controlled model file paths before passing them to the Weka Java Virtual Machine (JVM) via command-line arguments. When operating under the nltk.pathsec security sandbox, this omission permits attackers who control model path parameters to read, probe, or overwrite arbitrary files outside authorized NLTK data directories.

Alon Barad
Alon Barad
7 views•10 min read
•about 4 hours ago•CVE-2026-35002
9.8

CVE-2026-35002: Eval Injection and Arbitrary Code Execution in Agno Agent Platform

CVE-2026-35002 is an arbitrary code execution vulnerability in the Agno agent platform (formerly Phidata) affecting all versions prior to 2.3.24. The flaw resides in the model execution logic (libs/agno/agno/models/base.py) and tool serialization logic (libs/agno/agno/tools/function.py), where the application directly passes the field_type string parameter into Python's built-in eval() function to resolve string type names into Python data types. An attacker who can influence FunctionCall arguments via prompt injection or supply untrusted dictionary structures for deserialization can execute arbitrary Python expressions within the context of the host process.

Amit Schendel
Amit Schendel
4 views•9 min read
•3 days ago•CVE-2026-115895
8.8

CVE-2026-115895: Code Injection via Unsanitized Import Alias in Happy DOM ECMAScript Module Compiler

CVE-2026-115895 is a high-severity code injection vulnerability (CWE-94) in the happy-dom npm package prior to version 21.0.4. The vulnerability exists within the ECMAScriptModuleCompiler component, which transforms ECMAScript module syntax into executable Node.js functions when JavaScript evaluation is enabled. Due to an incomplete fix for CVE-2026-33943, the compiler failed to validate local named import aliases against legal JavaScript identifier rules. Attackers supplying crafted module imports—specifically utilizing destructuring default initializers alongside JSON module attributes—can inject arbitrary JavaScript expressions directly into the host Node.js runtime context.

Amit Schendel
Amit Schendel
11 views•10 min read
•3 days ago•CVE-2026-115883
6.0

CVE-2026-115883: Cross-Namespace Access via Dot-Delimited Labels in LangGraph Postgres and SQLite Stores

An improper input validation and authorization bypass vulnerability exists in the LangGraph storage layer affecting langgraph, langgraph-checkpoint-postgres, and langgraph-checkpoint-sqlite. Because relational store implementations serialize namespace tuples into dot-delimited strings without validating labels on read, search, delete, and batch operations, an attacker who controls a namespace label can inject periods (.) to traverse namespace boundaries and access, modify, or delete data belonging to other namespaces.

Amit Schendel
Amit Schendel
9 views•9 min read