{"id":"CVE-2026-8723","sourceIdentifier":"7ffcee3d-2c14-4c3e-b844-86c6a321a158","published":"2026-05-17T00:16:21.233","lastModified":"2026-10-23T23:10:00.157","vulnStatus":"Awaiting Analysis","cveTags":[],"descriptions":[{"lang":"en","value":"### Summary\n\n\n\n`qs.stringify` throws `TypeError` when called with `arrayFormat: 'comma'` and `encodeValuesOnly: true` on an array containing `null` or `undefined`. The throw is synchronous and not handled by any of qs's null-related options (`skipNulls`, `strictNullHandling`).\n\n\n\n### Details\n\n\n\nIn the comma + `encodeValuesOnly` branch, `lib/stringify.js:145` mapped the array through the raw encoder before joining:\n\n\n\n```js\n\n\n\nobj = utils.maybeMap(obj, encoder);\n\n\n\n```\n\n\n\n`utils.encode` (`lib/utils.js:195`) reads `str.length` with no null guard, so a `null` or `undefined` element throws `TypeError`. `skipNulls` and `strictNullHandling` are both checked in the per-element loop below this line and never get a chance to run.\n\n\n\nSame class of bug as the filter-array path fixed in 0c180a4. The vulnerable shape of the comma + `encodeValuesOnly` branch was introduced in 4c4b23d (\"encode comma values more consistently\", PR #463, 2023-01-19), first released in v6.11.1.\n\n\n\n#### PoC\n\n\n\n```js\n\n\n\nconst qs = require('qs');\n\n\n\nqs.stringify({ a: [null, 'b'] },      { arrayFormat: 'comma', encodeValuesOnly: true });\n\n\n\nqs.stringify({ a: [undefined, 'b'] }, { arrayFormat: 'comma', encodeValuesOnly: true });\n\n\n\nqs.stringify({ a: [null] },           { arrayFormat: 'comma', encodeValuesOnly: true });\n\n\n\n// TypeError: Cannot read properties of null (reading 'length')\n\n\n\n//     at encode (lib/utils.js:195:13)\n\n\n\n//     at Object.maybeMap (lib/utils.js:322:37)\n\n\n\n//     at stringify (lib/stringify.js:145:25)\n\n\n\n```\n\n\n\n#### Fix\n\n\n\n`lib/stringify.js:145`, applied in 21f80b3 on `main` and released as v6.15.2:\n\n\n\n```diff\n\n\n\n- obj = utils.maybeMap(obj, encoder);\n\n\n\n+ obj = utils.maybeMap(obj, function (v) {\n\n\n\n+     return v == null ? v : encoder(v);\n\n\n\n+ });\n\n\n\n```\n\n\n\n`null` and `undefined` now pass through `maybeMap` unchanged and reach the `join(',')` step as-is. For `{ a: [null, 'b'] }` this produces `a=,b`, matching the non-`encodeValuesOnly` comma path (which already joins before encoding and produces `a=%2Cb` for the same input). Single-element `[null]` arrays still collapse via the existing `obj.join(',') || null` and remain subject to `skipNulls` / `strictNullHandling` in the main loop.\n\n\n\n### Affected versions\n\n\n\n`>=6.11.1 <6.15.2` — fixed in v6.15.2.\n\n\n\nThe vulnerable code shape was introduced in 4c4b23d and first shipped in v6.11.1. Earlier versions — including all of 6.7.x, 6.8.x, 6.9.x, 6.10.x, and 6.11.0 — implemented the comma + `encodeValuesOnly` path differently (joining before encoding) and are not affected. Empirically verified across released versions.\n\n\n\n### Impact\n\n\n\nApplication code that calls `qs.stringify` with both `arrayFormat: 'comma'` and `encodeValuesOnly: true` (both non-default) on input that may contain a `null` or `undefined` array element will throw synchronously instead of producing a query string. In a typical Node.js HTTP framework (Express, Fastify, Koa, hapi) the sync throw is caught by the framework's error boundary and the affected request returns a 500; the worker process does not exit and subsequent requests are unaffected. The \"kills the worker process\" framing applies only to call sites outside a request-handler error boundary (background jobs, startup paths, stream pipelines) or to deployments with framework error handling explicitly disabled.\n\n\n\nThe vulnerable input is a `null` or `undefined` entry inside an array; this is reachable from JSON request bodies or from application code constructing arrays from user input, but not from standard HTML form submissions (which produce strings or omitted fields, not literal `null`)."},{"lang":"es","value":"### Resumen\n\n`qs.stringify` lanza `TypeError` cuando se llama con `arrayFormat: 'comma'` y `encodeValuesOnly: true` en un array que contiene `null` o `undefined`. El lanzamiento es síncrono y no es manejado por ninguna de las opciones de qs relacionadas con nulos (`skipNulls`, `strictNullHandling`).\n\n### Detalles\n\nEn la rama de coma + `encodeValuesOnly`, `lib/stringify.js:145` mapeó el array a través del codificador en bruto antes de unir:\n\n```js\nobj = utils.maybeMap(obj, encoder);\n```\n\n`utils.encode` (`lib/utils.js:195`) lee `str.length` sin una protección contra nulos, por lo que un elemento `null` o `undefined` lanza `TypeError`. `skipNulls` y `strictNullHandling` se verifican en el bucle por elemento debajo de esta línea y nunca tienen la oportunidad de ejecutarse.\n\nMisma clase de error que la ruta de filtro de array corregida en 0c180a4. La forma vulnerable de la rama de coma + `encodeValuesOnly` se introdujo en 4c4b23d ('encode comma values more consistently', PR #463, 2023-01-19), lanzada por primera vez en v6.11.1.\n\n#### PoC\n\n```js\nconst qs = require('qs');\n\nqs.stringify({ a: [null, 'b'] },      { arrayFormat: 'comma', encodeValuesOnly: true });\n\nqs.stringify({ a: [undefined, 'b'] }, { arrayFormat: 'comma', encodeValuesOnly: true });\n\nqs.stringify({ a: [null] },           { arrayFormat: 'comma', encodeValuesOnly: true });\n\n// TypeError: Cannot read properties of null (reading 'length')\n\n//     at encode (lib/utils.js:195:13)\n\n//     at Object.maybeMap (lib/utils.js:322:37)\n\n//     at stringify (lib/stringify.js:145:25)\n```\n\n#### Corrección\n\n`lib/stringify.js:145`, aplicado en 21f80b3 en `main` y lanzado como v6.15.2:\n\n```diff\n- obj = utils.maybeMap(obj, encoder);\n+ obj = utils.maybeMap(obj, function (v) {\n+     return v == null ? v : encoder(v);\n+ });\n```\n\n`null` y `undefined` ahora pasan por `maybeMap` sin cambios y llegan al paso `join(',')` tal cual. Para `{ a: [null, 'b'] }` esto produce `a=,b`, coincidiendo con la ruta de coma sin `encodeValuesOnly` (que ya une antes de codificar y produce `a=%2Cb` para la misma entrada). Los arrays de un solo elemento `[null]` todavía se colapsan a través del `obj.join(',') || null` existente y permanecen sujetos a `skipNulls` / `strictNullHandling` en el bucle principal.\n\n### Versiones afectadas\n\n`>=6.11.1 anterior a 6.15.2` — corregido en v6.15.2.\n\nLa forma de código vulnerable se introdujo en 4c4b23d y se envió por primera vez en v6.11.1. Versiones anteriores — incluyendo todas las 6.7.x, 6.8.x, 6.9.x, 6.10.x y 6.11.0 — implementaron la ruta de coma + `encodeValuesOnly` de manera diferente (uniendo antes de codificar) y no están afectadas. Verificado empíricamente en todas las versiones lanzadas.\n\n### Impacto\n\nEl código de aplicación que llama a `qs.stringify` con `arrayFormat: 'comma'` y `encodeValuesOnly: true` (ambos no predeterminados) en una entrada que puede contener un elemento de array `null` o `undefined` lanzará de forma síncrona en lugar de producir una cadena de consulta. En un framework HTTP típico de Node.js (Express, Fastify, Koa, hapi), el lanzamiento síncrono es capturado por el límite de error del framework y la solicitud afectada devuelve un 500; el proceso del trabajador no se cierra y las solicitudes posteriores no se ven afectadas. El encuadre de 'mata el proceso del trabajador' se aplica solo a los sitios de llamada fuera de un límite de error del gestor de solicitudes (trabajos en segundo plano, rutas de inicio, tuberías de flujo) o a implementaciones con el manejo de errores del framework explícitamente deshabilitado.\n\nLa entrada vulnerable es una entrada `null` o `undefined` dentro de un array; esto es alcanzable desde cuerpos de solicitud JSON o desde código de aplicación que construye arrays a partir de la entrada del usuario, pero no desde envíos de formularios HTML estándar (que producen cadenas o campos omitidos, no `null` literal)."}],"affected":[{"source":"7ffcee3d-2c14-4c3e-b844-86c6a321a158","affectedData":[{"vendor":"ljharb","product":"qs","defaultStatus":"unaffected","collectionURL":"https://npmjs.com/qs","packageName":"qs","repo":"https://github.com/ljharb/qs","versions":[{"version":"6.11.1","lessThan":"6.15.2","versionType":"semver","status":"affected"}]}]}],"metrics":{"cvssMetricV40":[{"source":"7ffcee3d-2c14-4c3e-b844-86c6a321a158","type":"Secondary","cvssData":{"version":"4.0","vectorString":"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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","baseScore":6.3,"baseSeverity":"MEDIUM","attackVector":"NETWORK","attackComplexity":"LOW","attackRequirements":"PRESENT","privilegesRequired":"NONE","userInteraction":"NONE","vulnConfidentialityImpact":"NONE","vulnIntegrityImpact":"NONE","vulnAvailabilityImpact":"LOW","subConfidentialityImpact":"NONE","subIntegrityImpact":"NONE","subAvailabilityImpact":"NONE","exploitMaturity":"NOT_DEFINED","confidentialityRequirement":"NOT_DEFINED","integrityRequirement":"NOT_DEFINED","availabilityRequirement":"NOT_DEFINED","modifiedAttackVector":"NOT_DEFINED","modifiedAttackComplexity":"NOT_DEFINED","modifiedAttackRequirements":"NOT_DEFINED","modifiedPrivilegesRequired":"NOT_DEFINED","modifiedUserInteraction":"NOT_DEFINED","modifiedVulnConfidentialityImpact":"NOT_DEFINED","modifiedVulnIntegrityImpact":"NOT_DEFINED","modifiedVulnAvailabilityImpact":"NOT_DEFINED","modifiedSubConfidentialityImpact":"NOT_DEFINED","modifiedSubIntegrityImpact":"NOT_DEFINED","modifiedSubAvailabilityImpact":"NOT_DEFINED","Safety":"NOT_DEFINED","Automatable":"NOT_DEFINED","Recovery":"NOT_DEFINED","valueDensity":"NOT_DEFINED","vulnerabilityResponseEffort":"NOT_DEFINED","providerUrgency":"NOT_DEFINED"}}],"cvssMetricV31":[{"source":"7ffcee3d-2c14-4c3e-b844-86c6a321a158","type":"Secondary","cvssData":{"version":"3.1","vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","baseScore":5.3,"baseSeverity":"MEDIUM","attackVector":"NETWORK","attackComplexity":"LOW","privilegesRequired":"NONE","userInteraction":"NONE","scope":"UNCHANGED","confidentialityImpact":"NONE","integrityImpact":"NONE","availabilityImpact":"LOW"},"exploitabilityScore":3.9,"impactScore":1.4}],"ssvcV203":[{"source":"134c704f-9b21-4f2e-91b3-4a467353bcc0","ssvcData":{"timestamp":"2026-05-18T14:02:12.499337Z","id":"CVE-2026-8723","options":[{"exploitation":"poc"},{"automatable":"yes"},{"technicalImpact":"partial"}],"role":"CISA Coordinator","version":"2.0.3"}}]},"weaknesses":[{"source":"7ffcee3d-2c14-4c3e-b844-86c6a321a158","type":"Secondary","description":[{"lang":"en","value":"CWE-476"}]}],"references":[{"url":"https://github.com/ljharb/qs/commit/21f80b33e5c8b3f7eba1034fff0da4a4a37a1d41","source":"7ffcee3d-2c14-4c3e-b844-86c6a321a158"},{"url":"https://github.com/ljharb/qs/security/advisories/GHSA-q8mj-m7cp-5q26","source":"7ffcee3d-2c14-4c3e-b844-86c6a321a158"},{"url":"https://github.com/ljharb/qs/security/advisories/GHSA-q8mj-m7cp-5q26","source":"134c704f-9b21-4f2e-91b3-4a467353bcc0"}]}