Skip to main content
VulnerabilityPublished Updated

CVE-2026-41673: xmldom · Uncontrolled recursion in XML serialization leads to DoS

Affects @xmldom/xmldomSource GCVE Database

0

Not known to be exploited · CVSS 8.7 High : upgrade to 0.8.13 soon.

Data as of 27 Oct 2026, 00:07 UTC

CVSS 4.0 base score
8.7High
How severe the flaw is on the 0–10 CVSS scale.
Patch
Available – fixed in 0.8.13, 0.9.10
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

Exploitation evidence

  1. Disclosed: evidence found.

    CVE published 22 Apr 2026

  2. Detectable: no evidence on record.

    No public nuclei template indexed

  3. Weaponised: no evidence on record.

    No public exploit code indexed in Metasploit or Exploit-DB

  4. Exploited in the wild: no evidence on record.

    Not in CISA KEV and no source reports exploitation

  5. Observed by OffSeq Mirage: no evidence on record.

    No attack traffic recorded by OffSeq Mirage sensors

A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), the Metasploit, Exploit-DB and nuclei-templates index as of 26 Oct 2026.

What to do

Upgrade @xmldom/xmldom to 0.8.13; @xmldom/xmldom to 0.9.10.

Affected software

ProductAffectedFixed in
npm@xmldom/xmldomAffected
  • <0.8.13
Fixed in
  • 0.8.13
npm@xmldom/xmldomAffected
  • >=0.9.0 <0.9.10
Fixed in
  • 0.9.10
npmxmldomAffected
  • <=0.6.0
Fixed in–

Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.

Description

## Summary Seven recursive traversals in `lib/dom.js` operate without a depth limit. A sufficiently deeply nested DOM tree causes a `RangeError: Maximum call stack size exceeded`, crashing the application. **Reported operations:** - `Node.prototype.normalize()` — reported by @praveen-kv (email 2026-04-05) and @KarimTantawey (GHSA-fwmp-8wwc-qhv6, via `DOMParser.parseFromString()`) - `XMLSerializer.serializeToString()` — reported by @Jvr2022 (GHSA-2v35-w6hq-6mfw) and @KarimTantawey (GHSA-j2hf-fqwf-rrjf) **Additionally, discovered in research:** - `Element.getElementsByTagName()` / `getElementsByTagNameNS()` / `getElementsByClassName()` / `getElementById()` - `Node.cloneNode(true)` - `Document.importNode(node, true)` - `node.textContent` (getter) - `Node.isEqualNode(other)` All seven share the same root cause: pure-JavaScript recursive tree traversal with no depth guard. A single deeply nested document (parsed successfully) triggers any or all of these operations. --- ## Details ### Root cause `lib/dom.js` implements DOM tree traversals as depth-first recursive functions. Each level of element nesting adds one JavaScript call frame. The JS engine's call stack is finite; once exhausted, a `RangeError: Maximum call stack size exceeded` is thrown. This error may not be caught reliably at stack-exhaustion depths because the catch handler itself requires stack frames to execute — especially in async scenarios, where an uncaught `RangeError` inside a callback or promise chain can crash the entire Node.js process. Parsing a deeply nested document **succeeds** — the SAX parser in `lib/sax.js` is iterative. The crash occurs during subsequent operations on the parsed DOM. ### `Node.prototype.normalize()` — reported by @praveen-kv [`lib/dom.js:1296–1308`](https://github.com/xmldom/xmldom/blob/9ef2fd297ca527a05ecb11979850317a927cd20c/lib/dom.js#L1296-L1308) (main): ```js normalize: function () { var child = this.firstChild; while (child) { var next = child.nextSibling; if (next && next.nodeType == TEXT_NODE && child.nodeType == TEXT_NODE) { this.removeChild(next); child.appendData(next.data); } else { child.normalize(); // recursive call — no depth guard child = next; } } }, ``` Crash threshold (Node.js 18, default stack): ~10,000 levels. ### `XMLSerializer.serializeToString()` — reported by @Jvr2022 [`lib/dom.js:2790–2974`](https://github.com/xmldom/xmldom/blob/9ef2fd297ca527a05ecb11979850317a927cd20c/lib/dom.js#L2790-L2974) (main): The internal `serializeToString` worker recurses into child nodes at four call sites, each passing a `visibleNamespaces.slice()` copy. The per-frame allocation causes earlier stack exhaustion than `normalize()`. Crash threshold (Node.js 18, default stack): ~5,000 levels. ### Additional recursive entry points All five crash at ~10,000 levels on Node.js 18. | Function | Definition | Public API entry point(s) | Crash depth (Node.js 18) | |-----------------------------|----------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|--------------------------| | `_visitNode` | [`lib/dom.js:1529`](https://github.com/xmldom/xmldom/blob/9ef2fd297ca527a05ecb11979850317a927cd20c/lib/dom.js#L1529) | `getElementsByTagName()`, `getElementsByTagNameNS()`, `getElementsByClassName()`, `getElementById()` | ~10,000 levels | | `cloneNode` (module fn) | [`lib/dom.js:3037`](https://github.com/xmldom/xmldom/blob/9ef2fd297ca527a05ecb11979850317a927cd20c/lib/dom.js#L3037) | `Node.prototype.cloneNode(true)` | ~10,000 levels | | `importNode` (module fn) | [`lib/dom.js:2975`](https://github.com/xmldom/xmldom/blob/9ef2fd297ca527a05ecb11979850317a927cd20c/lib/dom.js#L2975) | `Document.prototype.importNode(node, true)` | ~10,000 levels | | `getTextContent` (inner fn) | [`lib/dom.js:3130`](https://github.com/xmldom/xmldom/blob/9ef2fd297ca527a05ecb11979850317a927cd20c/lib/dom.js#L3130) | `node.textContent` (getter) | ~10,000 levels | | `isEqualNode` | [`lib/dom.js:1120`](https://github.com/xmldom/xmldom/blob/9ef2fd297ca527a05ecb11979850317a927cd20c/lib/dom.js#L1120) | `Node.prototype.isEqualNode(other)` | ~10,000 levels | Both active branches (`main` and `release-0.8.x`) are identically affected. The unscoped `xmldom`

References

Community reviews

Loading reviews…