GHSA-x4fp-j954-r2f4: Npm/@xmldom/xmldom vulnerability
Summary
On the @xmldom/xmldom 0.8.x line, parsing an XML end tag whose name is followed by a long run of whitespace and then a non-whitespace character triggers quadratic-time regular-expression backtracking (ReDoS), so a single small crafted end tag stalls the Node.js event loop. It is reachable from DOMParser.parseFromString under default options, unauthenticated, before any validity check — an availability-only denial of service. The 0.9.x line is not affected.
Details
lib/sax.js (release-0.8.x, commit e5c1480) trims trailing whitespace from a captured end-tag name with an unanchored global regex:
- lib/sax.js line 120: https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/sax.js#L120
js /[ \t\n\r]+$/g
Applied to a string shaped whitespace-run + one non-whitespace char (e.g. the content of an end tag </ … x>), the engine must, for every starting position, extend [ws]+ to the end and then fail the $ anchor when the trailing non-whitespace char is present — classic O(n²) backtracking in the length of the whitespace run. The trimmed substring is delimited only by indexOf('>'), so the attacker controls its length directly.
Proof of Concept
js const { DOMParser } = require('@xmldom/xmldom'); // 0.8.x const n = 64 1024; const payload = '<r></' + ' '.repeat(n) + 'x>'; console.time('parse'); new DOMParser().parseFromString(payload, 'text/xml'); console.timeEnd('parse');
Measured (Node 18) — time quadruples per doubling of the whitespace run (canonical O(n²)):
| Whitespace run | Isolated regex | End-to-end parseFromString (0.8.13) | |---|---|---| | 4 KB | 5.6 ms | 5.7 ms | | 8 KB | 22.7 ms | 22.5 ms | | 16 KB | 88.6 ms | 92 ms | | 32 KB | 354 ms | 361 ms | | 64 KB | 1434 ms | 1452 ms | | 128 KB | 5761 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.4 s at 64 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.
Affected Versions
Affected on the 0.7.x and 0.8.x lines (the trailing-whitespace trim was added in 0.7.0, present through 0.8.14); the fix targets the 0.8.x LTS patch. The 0.9.x line rewrote end-tag parsing to an anchored linear matcher and never had this regex, so it is not affected. No published unscoped xmldom is affected — the vulnerable code exists only in a 0.7.0 git tag that was never released to npm (npm view xmldom → latest = 0.6.0).
Fix Applied
Anchors the end-tag trailing-whitespace trim so it runs in linear time instead of backtracking quadratically on a long whitespace run. Byte-identical output. Non-breaking; 0.8.x-only.
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.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
npm/@xmldom/xmldomto a version that resolves this vulnerability.Fixed in 0.8.15 - Upgrade
Upgrade
@xmldom/xmldomto a version that resolves this vulnerability.Fixed in 0.8.14 - Compensating control
Apply an input-size limit (e.g., cap the size of XML documents and/or specifically cap the length of whitespace runs in end tags) before calling `DOMParser.parseFromString`, because `@xmldom/xmldom` applies no input-size limit and an attacker-controlled long whitespace run can stall the Node.js event loop.
Event History
Frequently Asked Questions
Which deployments are exposed?
Applications using the 0.8.x line are affected when they parse XML with DOMParser.parseFromString. The 0.9.x line is not affected, and the vulnerable parsing path is enabled under default options.
What must an attacker be able to do to trigger the issue?
An attacker needs to supply XML that the application passes to DOMParser.parseFromString. No authentication is required, and the crafted end tag is processed before XML validity checks.
What is a practical mitigation if an upgrade cannot be applied immediately?
Do not pass untrusted XML to the affected parser. In particular, reject or otherwise prevent end tags containing a long whitespace run followed by a non-whitespace character, as this input shape triggers the expensive regular-expression processing.
How can I determine whether a service is already at risk?
Check the installed @xmldom/xmldom version and identify whether the service parses attacker-controlled XML through DOMParser.parseFromString. Versions in the 0.8.x line meet the affected condition; 0.9.x does not.