GitHub Advisoryadvisory · npm

GHSA-965w-775f-mr7g xmldom: Quadratic-memory consumption

HIGH Published 2026-09-08 Modified 2026-09-08 Official page ↗
Aliases
CVE-2026-83615
Ecosystem
npm
Packages
@xmldom/xmldom, xmldom
Severity
High
CVSS 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
Weaknesses
CWE-770
Reviewed
2026-09-08

Affected packages

• npm @xmldom/xmldom: >= 0.7.0, < 0.8.15 → fixed in 0.8.15 • npm @xmldom/xmldom: >= 0.9.0, < 0.9.12 → fixed in 0.9.12 • npm xmldom: >= 0.1.5, <= 0.6.0 (no fix yet)

Description

Summary When an element declares a namespace prefix, xmldom copies the entire in-scope namespace map into a fresh object and keeps that copy on the element while it is open on the parse stack. A crafted document that nests N elements, each declaring one unique prefix, therefore drives the parser to hold on the order of N(N+1)/2 = O(N²) namespace-map entries at its peak, so a small, highly compressible input exhausts the heap. Parsing runs under default options on untrusted, network-delivered XML, so a sub-megabyte payload can OOM-crash the process before any application-level validation runs — an unauthenticated denial of service. Details `appendElement` performs the copy: `_copy` clones the current namespace map into a fresh object for each prefix-declaring element, and the copy is retained on that element's parse-stack entry: ```js if (localNSMap == null) { localNSMap = Object.create(null); _copy(currentNSMap, (currentNSMap = Object.create(null))); // full copy of all ancestor prefixes } currentNSMap[nsPrefix] = localNSMap[nsPrefix] = value; ... el.currentNSMap = currentNSMap; // retained while the element is open on the parse stack ``` https://github.com/xmldom/xmldom/blob/08a22d78e4bc50f12ce9f5090b8d96ee6031ac7b/lib/sax.js#L467-L540 The copies stack: the element at depth `i` copies a map of size ~`i`, and every ancestor stays live on the parse stack until it closes, so at the deepest point Σ`i` namespace entries are held at once. That peak is transient — the completed DOM retains only O(N), one small namespace map per node — but it is reached during parsing, which is what OOM-crashes the process. Proof of Concept A minimal document — N nested elements, each declaring one unique namespace prefix (no SAML wrapper needed): ```js const { DOMParser } = require('@xmldom/xmldom'); function build(n) { let open = '', close = ''; for (let i = 0; i `; close = '' + close; } return `${open}${close}`; // ......... } for (const n of [2000, 4000, 8000, 16000]) { const src = build(n); new DOMParser().parseFromString(src, 'text/xml'); // peak memory ~ O(n^2) console.log(n, (src.length / 1024).toFixed(0) + ' KB in', (process.resourceUsage().maxRSS / 1024).toFixed(0) + ' MB peak RSS'); } ``` Measured on Node.js v24 (peak RSS ~quadruples per doubling of depth; absolute numbers vary by host): | depth | input | peak RSS | | -----: | ------: | ------------------------------- | | 2,000 | 56 KB | 266 MB | | 4,000 | 115 KB | 622 MB | | 8,000 | 232 KB | 1.9 GB | | 16,000 | ~470 KB | OOM crash (default ~4 GB heap) | About 470 KB of trivially-generated, highly-compressible input crashes a default Node.js process; larger depths scale as O(N²) into the tens of GB, crashing larger hosts (as first measured by the reporter with a SAML-shaped payload). Impact Unauthenticated denial of service against any service that parses attacker-influenced XML with xmldom under default options. A single sub-megabyte request drives multi-gigabyte peak memory and can OOM-crash the process before any application-level validation (e.g. schema checks or a SAML signature verification) runs. The payload is a plain namespace-nesting document and highly compressible, so it is effective over compressed transports (e.g. an HTTP-Redirect / DEFLATE binding, not only POST bindings). Severity note The CVSS 4.0 vector scores availability only (`VC:N/VI:N/VA:H`): the flaw neither discloses nor alters data, it exhausts the heap. `VA:H` is justified because a single unauthenticated, network-delivered request (`AV:N/PR:N/UI:N`) of trivial complexity (`AC:L/AT:N`) drives the parser to multi-gigabyte peak memory and OOM-crashes the process before any application-level logic runs — a full loss of availability for the affected service. Fix Applied Inherit each element's in-scope namespace map through the prototype chain instead of copying it for every prefix-declaring element, so a deeply namespaced document holds O(N) namespace entries instead of O(N²) at peak. Behavior-preserving: serialized output is byte-identical, only the memory cost drops. Non-breaking and independent of `requireWellFormed`; ships on both maintained versions.

Related

Alias · CVE (1)

  • CVE-2026-83615 xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) DOMParser and XMLSerializer module. vulnerability

Weakness · CWE (1)

  • CWE-770 Allocation of Resources Without Limits or Throttling weakness

References