Public mode

No access to your tenant or devices. Every item links to its source. What this means

Sources need attention Customize Public modeDiscuss ChangeIntel on Discord

xmldom: Denial of service via uncontrolled recursion in XML serialization

CVSS 4.0 8.7 HIGHEUVD CVSS 4.0 8.7 CVSS 4.0 · [email protected] · Secondary 8.7 HIGHCVSS 3.1 · 0b0ca135-0b70-47e7-9f44-1890c2a1c46c · Secondary 7.5 HIGHCVSS_V4 Publisher rating HIGHFixed by @xmldom/xmldom 0.8.13Fixed by @xmldom/xmldom 0.9.10KEV status unknown

cve-program · CVE-2026-41673

Published

xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) `DOMParser` and `XMLSerializer` module. In @xmldom/xmldom prior to versions 0.9.10 and 0.8.13 and xmldom version 0.6.0 and prior, 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. This issue has been patched in versions @xmldom/xmldom versions 0.9.10 and 0.8.13.

Read the publisher's advisory

euvd · EUVD-2026-28288

Published

xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) `DOMParser` and `XMLSerializer` module. In @xmldom/xmldom prior to versions 0.9.10 and 0.8.13 and xmldom version 0.6.0 and prior, 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. This issue has been patched in versions @xmldom/xmldom versions 0.9.10 and 0.8.13.

Read the publisher's advisory

nvd · CVE-2026-41673

Published

xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) `DOMParser` and `XMLSerializer` module. In @xmldom/xmldom prior to versions 0.9.10 and 0.8.13 and xmldom version 0.6.0 and prior, 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. This issue has been patched in versions @xmldom/xmldom versions 0.9.10 and 0.8.13.

Read the publisher's advisory

osv · GHSA-2v35-w6hq-6mfw

Published

## 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` package (≤ 0.6.0) carries the same recursive patterns from its initial commit. ### Browser behavior Tested with Chromium 147 (Playwright headless). Chromium's native C++ implementations of all seven DOM methods are **iterative** — they traverse the DOM without consuming JS call stack frames. All seven succeed at depths up to 20,000 without any crash. When `@xmldom/xmldom` is bundled and run in a browser context the same recursive JS code executes under the browser's V8 stack limit (~12,000–13,000 frames). The crash thresholds are similar to those observed on Node.js 18 (~5,000 for `serializeToString`, ~10,000 for the remaining six). The vulnerability is specific to xmldom's pure-JavaScript recursive implementation, not an inherent property of the DOM operations. --- ## PoC ### `normalize()` (from @praveen-kv report, 2026-04-05) ```js const { DOMParser } = require('@xmldom/xmldom'); function generateNestedXML(depth) { return '<root>' + '<a>'.repeat(depth) + 'text' + '</a>'.repeat(depth) + '</root>'; } const doc = new DOMParser().parseFromString(generateNestedXML(10000), 'text/xml'); doc.documentElement.normalize(); // RangeError: Maximum call stack size exceeded ``` ### `XMLSerializer.serializeToString()` (from GHSA-2v35-w6hq-6mfw) ```js const { DOMParser, XMLSerializer } = require('@xmldom/xmldom'); const depth = 5000; const xml = '<a>'.repeat(depth) + '</a>'.repeat(depth); const doc = new DOMParser().parseFromString(xml, 'text/xml'); new XMLSerializer().serializeToString(doc); // RangeError: Maximum call stack size exceeded ``` The other methods have been verified using similar pocs. --- ## Impact Any service that accepts attacker-controlled XML and subsequently calls any of the seven affected DOM operations can be forced into a reliable denial of service with a single crafted payload. The immediate result is an uncaught `RangeError` and failed request processing. In deployments where uncaught exceptions terminate the worker or process, the impact can extend beyond a single request and disrupt service availability more broadly. No authentication, special options, or invalid XML is required. A valid, deeply nested XML document is enough. --- ## Disclosure The `normalize()` vector was publicly disclosed at 2026-04-06T11:25:07Z via [xmldom/xmldom#987](https://github.com/xmldom/xmldom/pull/987) (closed without merge). `serializeToString()` and the five additional recursive entry points were not mentioned in that PR. --- ## Fix Applied All seven affected traversals have been converted from recursive to iterative implementations, eliminating call-stack consumption on deep trees. ### `walkDOM` utility A new `walkDOM(node, context, callbacks)` utility is introduced. It traverses the subtree rooted at `node` in depth-first order using an explicit JavaScript array as a stack, consuming heap memory instead of call-stack frames. `context` is an arbitrary value threaded through the walk — each `callbacks.enter(node, context)` call returns the context to pass to that node's children, enabling per-branch state (e.g. namespace snapshots in the serializer). `callbacks.exit(node, context)` (optional) is called in post-order after all children have been visited. The following six operations are re-implemented on top of `walkDOM`: | Operation | Public entry point(s) | |---|---| | `_visitNode` helper | `getElementsByTagName()`, `getElementsByTagNameNS()`, `getElementsByClassName()`, `getElementById()` | | `getTextContent` inner function | `node.textContent` getter | | `cloneNode` module function | `Node.prototype.cloneNode(true)` | | `importNode` module function | `Document.prototype.importNode(node, true)` | | `serializeToString` worker | `XMLSerializer.prototype.serializeToString()`, `Node.prototype.toString()`, `NodeList.prototype.toString()` | | `normalize` | `Node.prototype.normalize()` | `normalize` uses `walkDOM` with a `null` context and an `enter` callback that merges adjacent Text children of the current node before `walkDOM` reads and queues those children — so the surviving post-merge children are what the walker descends into. ### Custom iterative loop for `isEqualNode` One function cannot use `walkDOM`: **`Node.prototype.isEqualNode(other)`** (0.9.x only; absent from 0.8.x) compares two trees in parallel. It maintains an explicit stack of `{node, other}` node pairs — one node from each tree — which cannot be expressed with `walkDOM`'s single-tree visitor. ### After the fix All seven entry points succeed on trees of arbitrary depth without throwing `RangeError`. The original PoCs still demonstrate the vulnerability on unpatched versions and confirm the fix on patched versions.

Read the publisher's advisory

Affected versions, risk and source history

Exploitation and risk

Source status

CISA KEV lists vulnerabilities known to be exploited. FIRST EPSS estimates the likelihood of exploitation in the next 30 days. An unavailable score does not mean zero risk.

Which CVEs have scores here?

This view includes predictions for CVE identifiers explicitly listed in public NuGet advisories. Microsoft release pages have their own prediction coverage. Check source status for the scope and latest checks.

No exploitation or prediction data is available here for these CVEs. This does not mean they are safe or unlikely to be exploited.

Risk history

Compare earlier KEV listings and daily EPSS scores. Advisory wording and affected versions have a separate history.

When does an EPSS score appear as a change?

A prediction change is reported when EPSS rises from below 10% to 10% or higher. Other daily score changes remain in this history.

Advisory history

13 saved versions

See how the publisher's advisory changed over time. Each link opens the version saved at that time. The publisher's update date can differ from the date shown here.

How advisory history works

Each link identifies one complete publisher record. History can include advisories that previously shared an identifier; an identifier removed by a publisher no longer establishes a current relationship. A historical link shows only that record and earlier saved versions. Failed checks and unchanged reads do not create new versions.

15 Nov 2026 13:02:49 UTC

xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) `DOMParser` and `XMLSerializer` module. In @xmldom/xmldom prior to versions 0.9.10 and 0.8.13 and xmldom version 0.6.0 and prior, seven recursive traversals in lib/dom.js…

euvd · EUVD-2026-28288 · Published

13 Nov 2026 12:01:21 UTC

xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) `DOMParser` and `XMLSerializer` module. In @xmldom/xmldom prior to versions 0.9.10 and 0.8.13 and xmldom version 0.6.0 and prior, seven recursive traversals in lib/dom.js…

euvd · EUVD-2026-28288 · Published

11 Nov 2026 11:02:13 UTC

xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) `DOMParser` and `XMLSerializer` module. In @xmldom/xmldom prior to versions 0.9.10 and 0.8.13 and xmldom version 0.6.0 and prior, seven recursive traversals in lib/dom.js…

euvd · EUVD-2026-28288 · Published

9 Nov 2026 10:09:47 UTC

xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) `DOMParser` and `XMLSerializer` module. In @xmldom/xmldom prior to versions 0.9.10 and 0.8.13 and xmldom version 0.6.0 and prior, seven recursive traversals in lib/dom.js…

euvd · EUVD-2026-28288 · Published

7 Nov 2026 09:31:22 UTC

xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) `DOMParser` and `XMLSerializer` module. In @xmldom/xmldom prior to versions 0.9.10 and 0.8.13 and xmldom version 0.6.0 and prior, seven recursive traversals in lib/dom.js…

euvd · EUVD-2026-28288 · Published

5 Nov 2026 08:58:42 UTC

xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) `DOMParser` and `XMLSerializer` module. In @xmldom/xmldom prior to versions 0.9.10 and 0.8.13 and xmldom version 0.6.0 and prior, seven recursive traversals in lib/dom.js…

euvd · EUVD-2026-28288 · Published

3 Nov 2026 08:03:19 UTC

xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) `DOMParser` and `XMLSerializer` module. In @xmldom/xmldom prior to versions 0.9.10 and 0.8.13 and xmldom version 0.6.0 and prior, seven recursive traversals in lib/dom.js…

euvd · EUVD-2026-28288 · Published

1 Nov 2026 07:10:30 UTC

xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) `DOMParser` and `XMLSerializer` module. In @xmldom/xmldom prior to versions 0.9.10 and 0.8.13 and xmldom version 0.6.0 and prior, seven recursive traversals in lib/dom.js…

euvd · EUVD-2026-28288 · Published

30 Oct 2026 06:53:33 UTC

xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) `DOMParser` and `XMLSerializer` module. In @xmldom/xmldom prior to versions 0.9.10 and 0.8.13 and xmldom version 0.6.0 and prior, seven recursive traversals in lib/dom.js…

euvd · EUVD-2026-28288 · Published

28 Oct 2026 05:10:40 UTC

xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) `DOMParser` and `XMLSerializer` module. In @xmldom/xmldom prior to versions 0.9.10 and 0.8.13 and xmldom version 0.6.0 and prior, seven recursive traversals in lib/dom.js…

euvd · EUVD-2026-28288 · Published

25 Oct 2026 09:17:41 UTC

xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) `DOMParser` and `XMLSerializer` module. In @xmldom/xmldom prior to versions 0.9.10 and 0.8.13 and xmldom version 0.6.0 and prior, seven recursive traversals in lib/dom.js…

nvd · CVE-2026-41673 · Published

24 Oct 2026 13:17:17 UTC

xmldom: Uncontrolled recursion in XML serialization leads to DoS

osv · GHSA-2v35-w6hq-6mfw · Published

23 Oct 2026 15:48:27 UTC

xmldom: Denial of service via uncontrolled recursion in XML serialization

cve-program · CVE-2026-41673 · Published

ChangeIntel

An IT change radar: releases, security, known issues, retirements, documentation changes, and service status from public sources. Every item links to supporting evidence; dates and statuses can change after they are read.

Sources read 15 Nov 12:11 UTC · 256 of 275 readable · 252 fully read · documentation 469/469 current