Skip to main content

@xmldom/xmldom CVE-2026-41673

| EUVDEUVD-2026-28288 HIGH
Uncontrolled Recursion (CWE-674)
2026-04-22 https://github.com/xmldom/xmldom GHSA-2v35-w6hq-6mfw
8.7
CVSS 4.0 · Vendor: https://github.com/xmldom/xmldom
Share

Severity by source

Vendor (https://github.com/xmldom/xmldom) PRIMARY
8.7 HIGH
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
SUSE
7.5 HIGH
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Red Hat
7.5 HIGH
qualitative

Primary rating from Vendor (https://github.com/xmldom/xmldom).

CVSS VectorVendor: https://github.com/xmldom/xmldom

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
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
X

Lifecycle Timeline

8
Source Code Evidence Fetched
May 07, 2026 - 04:47 vuln.today
Analysis Updated
May 07, 2026 - 04:47 vuln.today
v2 (cvss_changed)
Re-analysis Queued
May 07, 2026 - 04:35 vuln.today
cvss_changed
CVSS changed
May 07, 2026 - 04:35 NVD
8.7 (HIGH)
Analysis Generated
Apr 23, 2026 - 06:51 vuln.today
Analysis Generated
Apr 22, 2026 - 20:31 vuln.today
Patch released
Apr 22, 2026 - 20:31 nvd
Patch available
CVE Published
Apr 22, 2026 - 20:23 nvd
HIGH

Blast Radius

ecosystem impact
† from your stack dependencies † transitive graph · vuln.today resolves 4-path depth
  • 4 npm packages depend on @xmldom/xmldom (4 direct, 0 indirect)
  • 1,984 npm packages depend on xmldom (850 direct, 1,160 indirect)

Ecosystem-wide dependent count for version 0.9.0 and other introduced versions.

DescriptionCVE.org

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 (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 (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.

FunctionDefinitionPublic API entry point(s)Crash depth (Node.js 18)
_visitNodelib/dom.js:1529getElementsByTagName(), getElementsByTagNameNS(), getElementsByClassName(), getElementById()~10,000 levels
cloneNode (module fn)lib/dom.js:3037Node.prototype.cloneNode(true)~10,000 levels
importNode (module fn)lib/dom.js:2975Document.prototype.importNode(node, true)~10,000 levels
getTextContent (inner fn)lib/dom.js:3130node.textContent (getter)~10,000 levels
isEqualNodelib/dom.js:1120Node.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 (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:

OperationPublic entry point(s)
_visitNode helpergetElementsByTagName(), getElementsByTagNameNS(), getElementsByClassName(), getElementById()
getTextContent inner functionnode.textContent getter
cloneNode module functionNode.prototype.cloneNode(true)
importNode module functionDocument.prototype.importNode(node, true)
serializeToString workerXMLSerializer.prototype.serializeToString(), Node.prototype.toString(), NodeList.prototype.toString()
normalizeNode.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.

AnalysisAI

Denial of service in @xmldom/xmldom Node.js XML library allows remote attackers to crash applications via deeply nested XML documents. Seven DOM traversal methods (normalize, serializeToString, getElementsByTagName, cloneNode, importNode, textContent getter, isEqualNode) implement unbounded recursion consuming call stack frames until RangeError exception terminates the process. Exploitation requires no authentication - attackers send a single valid XML payload nested ~5,000-10,000 levels deep to trigger stack exhaustion in any subsequent DOM operation. Browser implementations of identical DOM methods use iterative C++ code and are unaffected. CVSS 8.7 High severity reflects network attack vector with no complexity barriers. Vendor-released patches (0.8.13, 0.9.10) replace all recursive traversals with iterative 'walkDOM' utility consuming heap instead of stack. Legacy unscoped 'xmldom' package (≤0.6.0) remains unfixed.

Technical ContextAI

@xmldom/xmldom is a pure-JavaScript implementation of W3C DOM Level 2 Core for Node.js environments, providing XML parsing and manipulation when native browser DOM APIs are unavailable. The library consists of two components: an iterative SAX parser (lib/sax.js) that successfully parses arbitrarily deep XML without stack consumption, and DOM tree manipulation functions (lib/dom.js) implemented as depth-first recursive functions. Each level of XML nesting adds one JavaScript call frame; V8's default stack limit (~10,000-13,000 frames in Node.js 18) creates a hard ceiling. CWE-674 (Uncontrolled Recursion) is the root cause - seven distinct tree-walking functions (normalize, serializeToString via internal worker, _visitNode helper used by four getElementsBy* variants, cloneNode/importNode module functions, getTextContent, isEqualNode) lack depth guards or iterative fallback logic. The vulnerability manifests only in JavaScript execution contexts; Chromium's native C++ DOM implementations of identical methods (tested at 20,000+ depth) traverse iteratively. CPE data identifies three affected package namespaces: scoped @xmldom/xmldom (primary, actively maintained), legacy unscoped xmldom (abandoned ≤0.6.0), and NPM ecosystem exposure through transitive dependencies in projects consuming either namespace.

RemediationAI

Upgrade to patched versions: @xmldom/xmldom 0.8.13 or later for 0.8.x users, or 0.9.10 or later for 0.9.x users, available from https://github.com/xmldom/xmldom/releases/tag/0.8.13 and https://github.com/xmldom/xmldom/releases/tag/0.9.10. Ten GitHub commits (17678a2, 291257, 2d6d691, 430357c, 4845ef1, 8834218, 8b7cfd1, b062038, e6edcab listed in advisory references) collectively rewrite all seven recursive traversals as iterative implementations using a new walkDOM utility function that maintains an explicit JavaScript array stack instead of consuming call frames, eliminating depth limits. For environments unable to immediately upgrade, implement XML depth validation before parsing: reject documents where maximum element nesting exceeds 1,000 levels using a streaming pre-parse check or SAX parser with depth counter (note: @xmldom/xmldom's own SAX parser does not expose depth to applications, requiring external validation). Compensating control trade-off: depth limits below 1,000 may break legitimate deeply-nested documents in specialized domains (academic XML corpora, auto-generated config files). Do NOT rely on try-catch wrappers around DOM operations - RangeError at stack exhaustion may not be catchable in async code paths and catch handlers themselves require stack frames. For legacy xmldom users (≤0.6.0) with no patch available: migrate to @xmldom/xmldom 0.8.13+ or replace with alternative maintained libraries (jsdom, libxmljs2) that use native/iterative DOM implementations. Container-level mitigation: configure Node.js --stack-size parameter to crash earlier with smaller payloads, providing faster failure detection but not preventing DoS.

CVE-2024-55591 CRITICAL POC
9.8 Jan 14

FortiOS and FortiProxy contain an authentication bypass via the Node.js websocket module allowing unauthenticated remote

CVE-2014-7205 CRITICAL POC
10.0 Oct 08

Eval injection vulnerability in the internals.batch function in lib/batch.js in the bassmaster plugin before 1.5.2 for t

CVE-2025-59528 CRITICAL POC
10.0 Sep 22

Flowise version 3.0.5 contains a remote code execution vulnerability in the CustomMCP node. The mcpServerConfig paramete

CVE-2017-14849 HIGH POC
7.5 Sep 28

Node.js 8.5.0 before 8.6.0 allows remote attackers to access unintended files, because a change to ".." handling was inc

CVE-2017-5941 CRITICAL POC
9.8 Feb 09

An issue was discovered in the node-serialize package 0.0.4 for Node.js. Rated critical severity (CVSS 9.8), this vulner

CVE-2014-3744 HIGH POC
7.5 Oct 23

Directory traversal vulnerability in the st module before 0.2.5 for Node.js allows remote attackers to read arbitrary fi

CVE-2014-9566 HIGH POC
7.5 Mar 10

Multiple SQL injection vulnerabilities in the Manage Accounts page in the AccountManagement.asmx service in the Solarwin

CVE-2013-4660 MEDIUM POC
6.8 Jun 28

The JS-YAML module before 2.0.5 for Node.js parses input without properly considering the unsafe !!js/function tag, whic

CVE-2015-5688 MEDIUM POC
5.0 Sep 04

Directory traversal vulnerability in lib/app/index.js in Geddy before 13.0.8 for Node.js allows remote attackers to read

CVE-2026-45321 CRITICAL POC
9.6 May 12

Credential-harvesting malware compromised 84 versions of 42 TanStack npm packages on 2026-05-11 via chained GitHub Actio

CVE-2014-7192 CRITICAL POC
10.0 Dec 11

Eval injection vulnerability in index.js in the syntax-error package before 1.1.1 for Node.js 0.10.x, as used in IBM Rat

CVE-2013-4450 MEDIUM POC
5.0 Oct 21

The HTTP server in Node.js 0.10.x before 0.10.21 and 0.8.x before 0.8.26 allows remote attackers to cause a denial of se

Vendor StatusVendor

SUSE

Severity: High
Product Status
SUSE Linux Enterprise Desktop 15 SP7 Fixed
SUSE Linux Enterprise High Performance Computing 15 SP7 Fixed
SUSE Linux Enterprise Module for Python 3 15 SP7 Fixed
SUSE Linux Enterprise Server 15 SP7 Fixed
SUSE Linux Enterprise Server for SAP Applications 15 SP7 Fixed

Share

CVE-2026-41673 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy