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

cve-program · CVE-2026-41673

Published

xmldom

Vendor product

Publisher's version information

Shown as published. These rules are not interpreted as NuGet package version ranges.

{
                    "vendor": "xmldom",
                    "product": "xmldom",
                    "versions": [
                        {
                            "version": "xmldom <= 0.6.0",
                            "status": "affected"
                        },
                        {
                            "version": "@xmldom/xmldom >= 0.9.0, < 0.9.10",
                            "status": "affected"
                        },
                        {
                            "version": "@xmldom/xmldom < 0.8.13",
                            "status": "affected"
                        }
                    ]
                }

Original source record · Cite this exact observation · Complete original publisher JSON

euvd · EUVD-2026-28288

Published

EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON.

Original publisher date fields

datePublished: May 7, 2026, 3:40:28 AM

dateUpdated: Aug 28, 2026, 12:04:19 PM

4 related source bulletins · separate from aliases

RHSA-2026:26234: Red Hat Security Advisory: Red Hat Developer Hub 1.9.5 release.

CVE-2026-6321 · CVE-2026-6322 · CVE-2026-24781 · CVE-2026-32281 · CVE-2026-41242 · CVE-2026-41672 · CVE-2026-41673 · CVE-2026-41674 · CVE-2026-41675 · CVE-2026-44293

RHSA-2026:60520: Red Hat Security Advisory: RHOAI 3.4.4 - Red Hat OpenShift AI

CVE-2025-66471 · CVE-2025-66626 · CVE-2025-71329 · CVE-2025-71330 · CVE-2026-0545 · CVE-2026-2614 · CVE-2026-5241 · CVE-2026-5422 · CVE-2026-6322 · CVE-2026-6734 · CVE-2026-8643 · CVE-2026-9697 · CVE-2026-12143 · CVE-2026-12151 · CVE-2026-12243 · CVE-2026-12481 · CVE-2026-13149 · CVE-2026-13311 · CVE-2026-13676 · CVE-2026-13717 · CVE-2026-14450 · CVE-2026-15154 · CVE-2026-15218 · CVE-2026-15378 · CVE-2026-15467 · CVE-2026-15581 · CVE-2026-16745 · CVE-2026-18608 · CVE-2026-18611 · CVE-2026-18617 · CVE-2026-18620 · CVE-2026-18621 · CVE-2026-18948 · CVE-2026-18951 · CVE-2026-18982 · CVE-2026-25681 · CVE-2026-27136 · CVE-2026-32280 · CVE-2026-32283 · CVE-2026-33079 · CVE-2026-33245 · CVE-2026-33811 · CVE-2026-33814 · CVE-2026-34993 · CVE-2026-35397 · CVE-2026-39820 · CVE-2026-39821 · CVE-2026-39828 · CVE-2026-39829 · CVE-2026-39830 · CVE-2026-39831 · CVE-2026-39835 · CVE-2026-40171 · CVE-2026-41672 · CVE-2026-41673 · CVE-2026-41675 · CVE-2026-42211 · CVE-2026-42215 · CVE-2026-42264 · CVE-2026-42266 · CVE-2026-42284 · CVE-2026-42305 · CVE-2026-42311 · CVE-2026-42338 · CVE-2026-42342 · CVE-2026-42499 · CVE-2026-42502 · CVE-2026-42508 · CVE-2026-42557 · CVE-2026-44017 · CVE-2026-44018 · CVE-2026-44020 · CVE-2026-44240 · CVE-2026-44244 · CVE-2026-44289 · CVE-2026-44290 · CVE-2026-44291 · CVE-2026-44292 · CVE-2026-44293 · CVE-2026-44486 · CVE-2026-44487 · CVE-2026-44488 · CVE-2026-44492 · CVE-2026-44494 · CVE-2026-44495 · CVE-2026-44496 · CVE-2026-44513 · CVE-2026-44660 · CVE-2026-44705 · CVE-2026-44724 · CVE-2026-44727 · CVE-2026-44827 · CVE-2026-44843 · CVE-2026-45292 · CVE-2026-45623 · CVE-2026-45736 · CVE-2026-45740 · CVE-2026-45804 · CVE-2026-46595 · CVE-2026-46597 · CVE-2026-46625 · CVE-2026-47214 · CVE-2026-48526 · CVE-2026-48710 · CVE-2026-48712 · CVE-2026-48779 · CVE-2026-48801 · CVE-2026-49476 · CVE-2026-49477 · CVE-2026-49851 · CVE-2026-49978 · CVE-2026-50193 · CVE-2026-53550 · CVE-2026-54058 · CVE-2026-54060 · CVE-2026-54283 · CVE-2026-54293 · CVE-2026-55379 · CVE-2026-55380 · CVE-2026-55685 · CVE-2026-56121 · CVE-2026-56208 · CVE-2026-56209 · CVE-2026-56210 · CVE-2026-56211 · CVE-2026-58049 · CVE-2026-59197 · CVE-2026-59199 · CVE-2026-59200 · CVE-2026-59204 · CVE-2026-59205 · CVE-2026-59869 · CVE-2026-59873 · CVE-2026-59874 · CVE-2026-59884 · CVE-2026-59885 · CVE-2026-59886 · CVE-2026-64835 · CVE-2026-64849 · CVE-2026-69152 · CVE-2026-69192 · CVE-2026-69243 · CVE-2026-69244

WID-SEC-W-2026-1833: IBM App Connect Enterprise (basic-ftp, xmldom): Mehrere Schwachstellen

CVE-2026-39983 · CVE-2026-41324 · CVE-2026-41672 · CVE-2026-41673 · CVE-2026-41674 · CVE-2026-41675

msrc_CVE-2026-41673: xmldom: Denial of service via uncontrolled recursion in XML serialization

CVE-2026-41673

No normalized affected-package data is projected for this source revision. The original publisher record may contain additional product and version evidence; this does not establish that a package is unaffected.

Original source record · Cite this exact observation · Complete original publisher JSON

nvd · CVE-2026-41673

Published

NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON.

Source analysis status: Deferred

Weakness classifications: CWE-674 · CWE-776

Original publisher date fields

published: 2026-05-07T04:16:33.257

lastModified: 2026-08-28T16:17:57.960

No normalized affected-package data is projected for this source revision. The original publisher record may contain additional product and version evidence; this does not establish that a package is unaffected.

Original source record · Cite this exact observation · Complete original publisher JSON

osv · GHSA-2v35-w6hq-6mfw

Published

@xmldom/xmldom

npm

SEMVER · introduced 0 → fixed 0.8.13

@xmldom/xmldom

npm

SEMVER · introduced 0.9.0 → fixed 0.9.10

xmldom

npm

SEMVER · introduced 0 → last_affected 0.6.0

Original source record · Cite this exact observation · Complete original publisher JSON

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