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/xmldom’s processing-instruction (PI) grammar regex exhibits quadratic-time backtracking
(ReDoS) when parsing an unterminated processing instruction. A single small XML document
containing <? + a target + a long run of whitespace and no closing ?> forces the regular
expression engine into O(n²) work, stalling the Node.js event loop. The input is parsed with
DOMParser.parseFromString under default options, so it is reachable from unauthenticated,
network-delivered XML (SOAP/SAML, webhooks, uploads, XML APIs).
Details
The PI production in lib/grammar.js compiles (flags mu) to:
^<\?(NameChars)(?:[\x20\x09\x0D\x0A]+([Char]*?))?\?>
^^^ S+ greedy ^^^ Char*? lazy
lib/grammar.jsline 261: https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/grammar.js#L261
In the optional tail (?:S+(Char*?))?, both the greedy separator S+ and the lazy data Char*?
match XML whitespace. When the required trailing ?> is absent, the engine must ultimately fail —
but first it tries every partition of the whitespace run between S+ and Char*?, which is O(n²)
in the length of the trailing whitespace.
The regex is executed against the entire remaining source string in two places in lib/sax.js,
so the whole whitespace tail is scanned:
parsePI— https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/sax.js#L680-L691parseProcessingInstruction— https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/sax.js#L862-L879
Affected Versions
Only the 0.9.x line is affected. lib/grammar.js (and this PI regex) was introduced in
commit 726b471 (“fix!: preserve DOCTYPE internal subset (#498)”), first released in
0.9.0-beta.9, and is unchanged through 0.9.10.
The 0.8.x line (≤ 0.8.13) and the unscoped xmldom package (≤ 0.6.0) parse PIs via a different
code path bounded by indexOf('?>') — they do not contain this regex and are not affected
by this issue. (They were not separately tested for a different PI ReDoS; the scope here is the
specific grammar.js regex.)
| Line | PI code path | Affected? |
|---|---|---|
0.9.x (0.9.0-beta.9 … 0.9.10) |
grammar.js PI regex over full remaining source |
Yes |
0.8.x (≤ 0.8.13) |
parseInstruction, bounded by indexOf('?>') |
No |
unscoped xmldom (≤ 0.6.0) |
older indexOf('?>')-bounded parsing |
No |
Proof of Concept
const { DOMParser } = require('@xmldom/xmldom');
const n = 32 * 1024;
const payload = '<a><?p' + ' '.repeat(n); // unterminated PI, no `?>`
console.time('parse');
new DOMParser().parseFromString(payload, 'text/xml');
console.timeEnd('parse');
Measured (Node 18), trailing whitespace after <?p, no ?> — time quadruples per doubling of
input length (canonical O(n²)):
| Trailing whitespace | g.PI.exec |
parseFromString |
|---|---|---|
| 2 KB | 4.4 ms | 5.1 ms |
| 4 KB | 16.9 ms | 17.0 ms |
| 8 KB | 111.4 ms | 66.3 ms |
| 16 KB | 263.8 ms | 336.5 ms |
| 32 KB | 1073.1 ms | — |
Impact
Availability only: a single parse of a small crafted document blocks the Node.js event loop for the duration of the quadratic scan (≈1 s at 32 KB; multi-second with larger inputs). No memory blow-up, no data exposure, no integrity impact. Because XML is routinely accepted from untrusted sources and parsed with default options, one request can stall a server.
Fix Applied
Fixed in @xmldom/xmldom 0.9.11 (0.9.x-only; the 0.8.x LTS line and the
unscoped xmldom package use a different, bounded PI code path and are not affected).
PR [#1039](https://github.com/xmldom/xmldom/pull/1039) inserts a fixed-width negative lookahead
(?!\s) immediately after the greedy S+, so the separator can no longer hand whitespace back to
the lazy data group:
- var PI = reg(/^<\?/, '(', Name, ')', regg(S, '(', Char, '*?)'), '?', /\?>/);
+ var PI = reg(/^<\?/, '(', Name, ')', regg(S, '(?!', _SChar, ')(', Char, '*?)'), '?', /\?>/);
The change is correct, minimal, and behavior-preserving: it produces identical [target, data]
captures on all valid PIs tested (incl. whitespace-heavy, tab/newline, empty-data, and xml-decl
cases) and removes the backtracking blow-up (linear, ~0.4 ms at 128 KB after the fix). The lookahead
is fixed-width and cannot itself backtrack — a strict improvement with no new parsing risk.
Severity note
The complexity is quadratic, not exponential, so a multi-second stall requires
tens-to-hundreds of KB of input. VA:H reflects that xmldom applies no input-size limit and the
path runs on default-options parsing, so a single unbounded parse can fully stall the event loop.
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 , 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 , 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-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 , hiero-sdk-js , install-expo-modules , jest-expo , jest-expo-enzyme , 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 , 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 , 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/xmldom @ 0.9.10 (+8099 more)