{"id":"EUVD-2026-101710","enisaUuid":"67f1ce93-b9b5-3152-964e-c086a6ab961f","description":"### Summary\n\n\n\n`qs.stringify` with `arrayFormat: \u0027comma\u0027` throws `TypeError: Cannot convert object to primitive value` when an array element cannot be coerced to a string, such as an object with an own non-callable `toString` (`{ \"toString\": \"x\" }`) or a null-prototype object. The comma format joins array elements with `Array.prototype.join` and, under `encodeValuesOnly`, first passes each element through `String()` in `utils.encode`; neither guards against an element whose string coercion fails.\n\n\n\n### Details\n\n\n\nIn `lib/stringify.js`, the comma branch collapses the array with `obj.join(\u0027,\u0027)`. For an element whose own `toString` is not callable, `ToPrimitive` skips it, falls through to `Object.prototype.valueOf` (which returns the object itself), and throws. Under `encodeValuesOnly: true`, the same element first reaches `String(str)` in `utils.encode` (`lib/utils.js`) and throws there. Nested arrays (`{ \"a\": [[{ \"toString\": \"x\" }]] }`) throw the same way, through `Array.prototype.toString`. The `indices`, `brackets`, and `repeat` formats walk into object elements instead of coercing them, and are not affected.\n\n\n\nSuch elements can come from untrusted input. `JSON.parse` produces `{ \"a\": [{ \"toString\": \"x\" }] }` with no qs option involved. `qs.parse(\"a[0][toString]\u003dx\", { allowPrototypes: true })` keeps the `toString` key, and `qs.parse(\"a[][b]\u003dc\", { plainObjects: true })` produces a null-prototype element; the default parse options drop `toString` and produce ordinary objects, which do not throw. Express 4\u0027s default `query parser`, Express 5\u0027s `extended` query parser, and body-parser\u0027s `urlencoded({ extended: true })` all call `qs.parse` with `allowPrototypes: true`.\n\n\n\n#### PoC\n\n\n\n```js\n\n\n\nvar qs \u003d require(\"qs\");\n\n\n\nqs.stringify(JSON.parse(\"{\\\"a\\\":[{\\\"toString\\\":\\\"x\\\"}]}\"), { arrayFormat: \"comma\" });\n\n\n\nqs.stringify(qs.parse(\"a[0][toString]\u003dx\", { allowPrototypes: true }), { arrayFormat: \"comma\" });\n\n\n\nqs.stringify(qs.parse(\"a[][b]\u003dc\", { plainObjects: true }), { arrayFormat: \"comma\", encodeValuesOnly: true });\n\n\n\n// TypeError: Cannot convert object to primitive value\n\n\n\n```\n\n\n\n#### Fix\n\n\n\nApplied in a952e75 on `main` and released as v6.16.1: an element whose string coercion would fail is coerced to its primitive `valueOf`, if any, or else to `Object.prototype.toString`, so it renders as `[object Object]`, the same as any other object element already does under the comma format. Nested arrays are walked the way `join` walks them. Every input that did not previously throw serializes exactly as before, and a `toString` or `valueOf` that itself throws still propagates.\n\n\n\n### Affected versions\n\n\n\n`\u003e\u003d6.7.0 \u003c6.16.1`, fixed in v6.16.1.\n\n\n\nThe `join` path was introduced with the comma format in b84acba and first shipped in v6.7.0; v6.6.x and earlier ignore `arrayFormat: \u0027comma\u0027` and are not affected. The `encodeValuesOnly` `String()` site was introduced in 4c4b23d (v6.11.1); before that, the same input throws at `join`. Every release from v6.7.0 through v6.16.0 is affected.\n\n\n\n### Impact\n\n\n\nAn unauthenticated request can make any code path that re-serializes attacker-influenced data with `qs.stringify` and `arrayFormat: \u0027comma\u0027` (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 bare `http` request handler, the process exits, so the impact in that case depends on the application error handling rather than on qs. The default `arrayFormat` (`indices`) is not affected.","datePublished":"Oct 14, 2026, 10:05:24 PM","dateUpdated":"Oct 16, 2026, 8:17:48 PM","baseScore":6.3,"baseScoreVersion":"4.0","baseScoreVector":"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","references":"https://github.com/ljharb/qs/security/advisories/GHSA-6pq8-jhm2-2vcw\nhttps://github.com/ljharb/qs/commit/a952e7519338ef0cfb25694a2988848f93333727\n","aliases":"CVE-2026-110816\nGHSA-6pq8-jhm2-2vcw\n","assigner":"harborist","epss":0.37,"enisaIdProduct":[{"id":"35fe0adb-56af-3f40-ab43-8855cd702068","product":{"name":"qs","vendor":{"name":"ljharb"}},"product_version":"6.7.0 \u003c6.16.1"}],"enisaIdVendor":[{"id":"a6c6780d-af13-38b6-88cc-638ae6c21c37","vendor":{"name":"ljharb"}}],"enisaIdVulnerability":[{"id":"b21b2e5a-7c76-378d-bcb1-20de842cb2df","vulnerability":{"id":"GHSA-6pq8-jhm2-2vcw","description":"qs : qs.stringify with arrayFormat \u0027comma\u0027 throws an uncaught TypeError on array elements carrying a non-callable own toString (DoS)","datePublished":"Oct 27, 2026, 4:10:55 PM","dateUpdated":"Oct 27, 2026, 4:10:55 PM","baseScore":0.0,"references":"https://github.com/ljharb/qs/security/advisories/GHSA-6pq8-jhm2-2vcw\nhttps://nvd.nist.gov/vuln/detail/CVE-2026-110816\nhttps://github.com/ljharb/qs/commit/a952e7519338ef0cfb25694a2988848f93333727\nhttps://github.com/ljharb/qs\nhttps://github.com/ljharb/qs/releases/tag/v6.16.1\n","enisa_id":"EUVD-2026-101710\n","aliases":"CVE-2026-110816\nGHSA-6pq8-jhm2-2vcw\n","epss":0.0,"dataProcessed":"Oct 29, 2026, 11:01:28 PM","vulnerabilityProduct":[],"vulnerabilityVendor":[]}},{"id":"bcdced32-d297-3edc-9859-b0a6393cc29a","vulnerability":{"id":"CVE-2026-110816","description":"### Summary\n\n\n\n`qs.stringify` with `arrayFormat: \u0027comma\u0027` throws `TypeError: Cannot convert object to primitive value` when an array element cannot be coerced to a string, such as an object with an own non-callable `toString` (`{ \"toString\": \"x\" }`) or a null-prototype object. The comma format joins array elements with `Array.prototype.join` and, under `encodeValuesOnly`, first passes each element through `String()` in `utils.encode`; neither guards against an element whose string coercion fails.\n\n\n\n### Details\n\n\n\nIn `lib/stringify.js`, the comma branch collapses the array with `obj.join(\u0027,\u0027)`. For an element whose own `toString` is not callable, `ToPrimitive` skips it, falls through to `Object.prototype.valueOf` (which returns the object itself), and throws. Under `encodeValuesOnly: true`, the same element first reaches `String(str)` in `utils.encode` (`lib/utils.js`) and throws there. Nested arrays (`{ \"a\": [[{ \"toString\": \"x\" }]] }`) throw the same way, through `Array.prototype.toString`. The `indices`, `brackets`, and `repeat` formats walk into object elements instead of coercing them, and are not affected.\n\n\n\nSuch elements can come from untrusted input. `JSON.parse` produces `{ \"a\": [{ \"toString\": \"x\" }] }` with no qs option involved. `qs.parse(\"a[0][toString]\u003dx\", { allowPrototypes: true })` keeps the `toString` key, and `qs.parse(\"a[][b]\u003dc\", { plainObjects: true })` produces a null-prototype element; the default parse options drop `toString` and produce ordinary objects, which do not throw. Express 4\u0027s default `query parser`, Express 5\u0027s `extended` query parser, and body-parser\u0027s `urlencoded({ extended: true })` all call `qs.parse` with `allowPrototypes: true`.\n\n\n\n#### PoC\n\n\n\n```js\n\n\n\nvar qs \u003d require(\"qs\");\n\n\n\nqs.stringify(JSON.parse(\"{\\\"a\\\":[{\\\"toString\\\":\\\"x\\\"}]}\"), { arrayFormat: \"comma\" });\n\n\n\nqs.stringify(qs.parse(\"a[0][toString]\u003dx\", { allowPrototypes: true }), { arrayFormat: \"comma\" });\n\n\n\nqs.stringify(qs.parse(\"a[][b]\u003dc\", { plainObjects: true }), { arrayFormat: \"comma\", encodeValuesOnly: true });\n\n\n\n// TypeError: Cannot convert object to primitive value\n\n\n\n```\n\n\n\n#### Fix\n\n\n\nApplied in a952e75 on `main` and released as v6.16.1: an element whose string coercion would fail is coerced to its primitive `valueOf`, if any, or else to `Object.prototype.toString`, so it renders as `[object Object]`, the same as any other object element already does under the comma format. Nested arrays are walked the way `join` walks them. Every input that did not previously throw serializes exactly as before, and a `toString` or `valueOf` that itself throws still propagates.\n\n\n\n### Affected versions\n\n\n\n`\u003e\u003d6.7.0 \u003c6.16.1`, fixed in v6.16.1.\n\n\n\nThe `join` path was introduced with the comma format in b84acba and first shipped in v6.7.0; v6.6.x and earlier ignore `arrayFormat: \u0027comma\u0027` and are not affected. The `encodeValuesOnly` `String()` site was introduced in 4c4b23d (v6.11.1); before that, the same input throws at `join`. Every release from v6.7.0 through v6.16.0 is affected.\n\n\n\n### Impact\n\n\n\nAn unauthenticated request can make any code path that re-serializes attacker-influenced data with `qs.stringify` and `arrayFormat: \u0027comma\u0027` (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 bare `http` request handler, the process exits, so the impact in that case depends on the application error handling rather than on qs. The default `arrayFormat` (`indices`) is not affected.","datePublished":"Oct 14, 2026, 10:05:24 PM","dateUpdated":"Oct 16, 2026, 8:17:48 PM","status":"PUBLISHED","baseScore":6.3,"baseScoreVersion":"4.0","baseScoreVector":"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","references":"https://github.com/ljharb/qs/security/advisories/GHSA-6pq8-jhm2-2vcw\nhttps://github.com/ljharb/qs/commit/a952e7519338ef0cfb25694a2988848f93333727\n","enisa_id":"EUVD-2026-101710\n","assigner":"harborist","epss":0.37,"dataProcessed":"Oct 29, 2026, 7:13:43 AM","vulnerabilityProduct":[{"id":"f46587cd-c9af-3d32-8c29-67bfc7f11c53","product":{"name":"qs","vendor":{"name":"ljharb"}},"product_version":"6.7.0 \u003c6.16.1"}],"vulnerabilityVendor":[{"id":"a2721e7a-daf3-3c96-9d1a-2133952d5ae2","vendor":{"name":"ljharb"}}]}}],"enisaIdAdvisory":[{"id":"0a419c98-42a8-313a-bd61-6d4598991145","advisory":{"id":"RHSA-2026:87077","description":"Red Hat Security Advisory: Red Hat Hardened Images RPMs Security Update","summary":"An update for Red Hat Hardened Images RPMs is now available.","datePublished":"Oct 21, 2026, 2:59:57 PM","dateUpdated":"Oct 21, 2026, 7:42:09 PM","baseScore":0.0,"references":"https://access.redhat.com/errata/RHSA-2026:87077\nhttps://security.access.redhat.com/data/csaf/v2/advisories/2026/rhsa-2026_87077.json\n","aliases":"CVE-2026-15577\nCVE-2026-33374\nCVE-2026-92539\nCVE-2026-93294\nCVE-2026-95502\nCVE-2026-100347\nCVE-2026-100348\nCVE-2026-100349\nCVE-2026-100351\nCVE-2026-100352\nCVE-2026-100353\nCVE-2026-100354\nCVE-2026-110816\n","source":{"id":9,"name":"csaf_redhat"},"advisoryProduct":[{"id":"5f1a0d9b-0fe1-3332-8c5f-7d2a272304e8","product":{"name":"Red Hat Hardened Images","vendor":{"name":"Red Hat"}}},{"id":"9ffad9db-9975-302f-bc87-180a0e507f2b","product":{"name":"grafana13.1-0:13.1.8-0.3.hum1@x86_64","vendor":{"name":"Red Hat"}}},{"id":"b3b55662-76eb-3a93-a447-3c0362fb32e6","product":{"name":"grafana13.1-0:13.1.8-0.3.hum1@src","vendor":{"name":"Red Hat"}}},{"id":"d9d9f30e-7c1f-3413-9095-a5f4054ec4a5","product":{"name":"grafana13.1-0:13.1.8-0.3.hum1@s390x","vendor":{"name":"Red Hat"}}},{"id":"da94b432-0233-3aa5-b005-60d231916edd","product":{"name":"grafana13.1-0:13.1.8-0.3.hum1@aarch64","vendor":{"name":"Red Hat"}}}]}},{"id":"1ed86306-aacf-3317-8227-cabda8b9ac93","advisory":{"id":"RHSA-2026:85266","description":"Red Hat Security Advisory: Red Hat Hardened Images RPMs Security Update","summary":"An update for Red Hat Hardened Images RPMs is now available.","datePublished":"Oct 19, 2026, 7:02:40 PM","dateUpdated":"Oct 21, 2026, 7:42:24 PM","baseScore":0.0,"references":"https://access.redhat.com/errata/RHSA-2026:85266\nhttps://security.access.redhat.com/data/csaf/v2/advisories/2026/rhsa-2026_85266.json\n","aliases":"CVE-2026-93450\nCVE-2026-100347\nCVE-2026-100348\nCVE-2026-100349\nCVE-2026-100351\nCVE-2026-100352\nCVE-2026-100353\nCVE-2026-100354\nCVE-2026-110816\n","source":{"id":9,"name":"csaf_redhat"},"advisoryProduct":[{"id":"39b28d33-c263-352c-909d-e66dfcea4619","product":{"name":"grafana13.2-0:13.2.3-0.3.hum1@aarch64","vendor":{"name":"Red Hat"}}},{"id":"60cc3524-7d6c-38c6-8cae-179b42ce169e","product":{"name":"grafana13.2-0:13.2.3-0.3.hum1@src","vendor":{"name":"Red Hat"}}},{"id":"bd3c067e-3a7e-3b44-bbcb-80a512e34e4d","product":{"name":"grafana13.2-0:13.2.3-0.3.hum1@x86_64","vendor":{"name":"Red Hat"}}},{"id":"c7342884-4506-3372-a476-1e94b785015b","product":{"name":"grafana13.2-0:13.2.3-0.3.hum1@s390x","vendor":{"name":"Red Hat"}}},{"id":"de0a7631-81a4-3778-ba4f-8437f287389b","product":{"name":"Red Hat Hardened Images","vendor":{"name":"Red Hat"}}}]}},{"id":"5ad7a41b-b834-3557-9018-eae8c0bdffa3","advisory":{"id":"msrc_CVE-2026-110816","description":"qs.stringify with arrayFormat comma throws TypeError on array elements that cannot be coerced to a string","datePublished":"Oct 17, 2026, 1:08:21 AM","dateUpdated":"Oct 17, 2026, 1:08:21 AM","baseScore":0.0,"references":"https://msrc.microsoft.com/csaf/vex/2026/msrc_cve-2026-110816.json\n","aliases":"CVE-2026-110816\n","source":{"id":20,"name":"csaf_microsoft"},"advisoryProduct":[{"id":"83d2fdd5-a001-33d3-be87-1e3ab199b34e","product":{"name":"3.0","vendor":{"name":"Wind River"}}},{"id":"e9f4f626-f76d-3fa2-9e8f-1d8123504bde","product":{"name":"Azure Linux","vendor":{"name":"Microsoft"}}},{"id":"f541e09b-366c-307e-8dd4-589627e8983b","product":{"name":"python-tensorboard","vendor":{"name":"Microsoft"}}}]}},{"id":"e1de9b10-8aa5-3e88-ae27-895f8dbddfdf","advisory":{"id":"openSUSE-SU-2026:12261-1","description":"git-bug-0.11.0-3.1 on GA media","summary":"git-bug-0.11.0-3.1 on GA media","datePublished":"Oct 24, 2026, 12:00:00 AM","dateUpdated":"Oct 26, 2026, 10:35:11 AM","baseScore":0.0,"references":"https://ftp.suse.com/pub/projects/security/csaf/opensuse-su-2026_12261-1.json\nhttps://www.suse.com/security/cve/CVE-2026-110095/\nhttps://www.suse.com/security/cve/CVE-2026-110180/\nhttps://www.suse.com/security/cve/CVE-2026-110306/\nhttps://www.suse.com/security/cve/CVE-2026-110725/\nhttps://www.suse.com/security/cve/CVE-2026-110816/\nhttps://www.suse.com/security/cve/CVE-2026-111756/\nhttps://www.suse.com/security/cve/CVE-2026-112085/\nhttps://www.suse.com/security/cve/CVE-2026-112366/\nhttps://www.suse.com/security/cve/CVE-2026-78659/\nhttps://www.suse.com/security/cve/CVE-2026-78660/\nhttps://www.suse.com/security/cve/CVE-2026-78663/\nhttps://www.suse.com/security/cve/CVE-2026-78669/\nhttps://www.suse.com/security/cve/CVE-2026-97032/\n","aliases":"CVE-2026-110095\nCVE-2026-110180\nCVE-2026-110306\nCVE-2026-110725\nCVE-2026-110816\nCVE-2026-111756\nCVE-2026-112085\nCVE-2026-112366\nCVE-2026-78659\nCVE-2026-78660\nCVE-2026-78663\nCVE-2026-78669\nCVE-2026-97032\n","source":{"id":21,"name":"csaf_opensuse"},"advisoryProduct":[{"id":"21104c49-7d3f-3ced-be4f-462121ba6e25","product":{"name":"git-bug-zsh-completion-0:0.11.0-3.1.aarch64","vendor":{"name":"SUSE"}}},{"id":"261d1262-7a24-3291-ae0a-cf8ee2897dc4","product":{"name":"git-bug-bash-completion-0:0.11.0-3.1.aarch64","vendor":{"name":"SUSE"}}},{"id":"28d1a614-d78f-35bd-9f31-5ba65ecb453d","product":{"name":"git-bug-fish-completion-0:0.11.0-3.1.aarch64","vendor":{"name":"SUSE"}}},{"id":"33b81846-9416-3a27-952d-8bd1156fe211","product":{"name":"git-bug-zsh-completion-0:0.11.0-3.1.ppc64le","vendor":{"name":"SUSE"}}},{"id":"5c4aadd7-834c-30b2-ab49-552098873dd1","product":{"name":"git-bug-zsh-completion-0:0.11.0-3.1.x86_64","vendor":{"name":"SUSE"}}},{"id":"60d370d1-d4b0-3c56-b042-195469125d2d","product":{"name":"openSUSE Tumbleweed","vendor":{"name":"SUSE"}}},{"id":"62b65c55-369a-3672-886b-123a976a8e2b","product":{"name":"git-bug-bash-completion-0:0.11.0-3.1.ppc64le","vendor":{"name":"SUSE"}}},{"id":"7c81aee8-593a-38b4-8975-33abe5c497d6","product":{"name":"git-bug-fish-completion-0:0.11.0-3.1.s390x","vendor":{"name":"SUSE"}}},{"id":"80c9da65-66fa-323b-8bbe-644e093c251c","product":{"name":"git-bug-fish-completion-0:0.11.0-3.1.ppc64le","vendor":{"name":"SUSE"}}},{"id":"8c6a5843-c889-36cf-bba2-5689af1ab0a8","product":{"name":"git-bug-fish-completion-0:0.11.0-3.1.x86_64","vendor":{"name":"SUSE"}}},{"id":"ad866fa6-f9fe-3b55-9f10-43df7a3511a8","product":{"name":"git-bug-bash-completion-0:0.11.0-3.1.x86_64","vendor":{"name":"SUSE"}}},{"id":"c3506d5e-604f-3709-8f58-7467a975031a","product":{"name":"git-bug-bash-completion-0:0.11.0-3.1.s390x","vendor":{"name":"SUSE"}}},{"id":"d0874260-b521-34ef-9cb8-77a62075c6cf","product":{"name":"git-bug-0:0.11.0-3.1.x86_64","vendor":{"name":"SUSE"}}},{"id":"da11e239-fa5d-38e8-b767-670a8ac03c2a","product":{"name":"git-bug-0:0.11.0-3.1.s390x","vendor":{"name":"SUSE"}}},{"id":"e111b8ed-0dd9-3beb-9e00-d8da2405d3f1","product":{"name":"git-bug-0:0.11.0-3.1.aarch64","vendor":{"name":"SUSE"}}},{"id":"e4659ed8-80d9-3a2d-a78e-ffcbaf6b0648","product":{"name":"git-bug-0:0.11.0-3.1.ppc64le","vendor":{"name":"SUSE"}}},{"id":"f218ef0e-8bbc-361a-bbee-55f04bbbb6a0","product":{"name":"git-bug-zsh-completion-0:0.11.0-3.1.s390x","vendor":{"name":"SUSE"}}}]}}]}