High

xmldom PI grammar regex ReDoS: quadratic backtracking on unterminated processing instructions

  • Disclosed: 08.09.2026
  • Updated: 11.10.2026 10:22

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

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:

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

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

Affected software

Click a technology to see all vulnerabilities affecting it.

PlatformPackageVulnerable versionsFixed in
npm package @xmldom/xmldom >= 0.9.0-beta.9, < 0.9.11 0.9.11

References

CVSS score -
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
Weakness (CWE)
CWE-1333, CWE-400
Platform
npm
Package
@xmldom/xmldom
Disclosed
08.09.2026
Updated
11.10.2026 10:22

Sources and licenses

OSV / GitHub Advisory Database - GHSA-g53g-w8rj-fmg7 - CC BY 4.0

Vulnerability history: @xmldom/xmldom

All published vulnerabilities of this package (npm package), newest first. Shows whether this is a one-off slip or a package that needs watching.

All (22) in the archive
Total entries
22
First → latest
08.2021 09.2026
Without a fix
0
Exploited in attacks
0

Severity breakdown

  • Critical 1
  • High 18
  • Medium 3

Disclosures per year

  1. 2021: 1
  2. 2022: 1
  3. 2023: 0
  4. 2024: 0
  5. 2025: 0
  6. 2026: 20
Severity:
  1. High CVE-2026-83616 >= 0.7.0, < 0.8.15, >= 0.9.0, < 0.9.12 0.8.15, 0.9.12
  2. High CVE-2026-83608 >= 0.7.0, < 0.8.15, >= 0.9.0, < 0.9.12 0.8.15, 0.9.12
  3. High CVE-2026-83613 >= 0.7.0, < 0.8.15, >= 0.9.0, < 0.9.12 0.8.15, 0.9.12
  4. High CVE-2026-83615 >= 0.7.0, < 0.8.15, >= 0.9.0, < 0.9.12 0.8.15, 0.9.12
  5. High CVE-2026-83612 >= 0.9.0-beta.1, < 0.9.12 0.9.12
  6. xmldom PI grammar regex ReDoS: quadratic backtracking on unterminated processing instructions this entry
    High CVE-2026-83606 >= 0.9.0-beta.9, < 0.9.11 0.9.11
  7. High CVE-2026-83607 >= 0.9.0, < 0.9.11, >= 0.7.0, < 0.8.14 0.9.11, 0.8.14
  8. High CVE-2026-83605 >= 0.9.0, < 0.9.11, >= 0.7.0, < 0.8.14 0.9.11, 0.8.14
  9. High CVE-2026-41673 < 0.8.13, >= 0.9.0, < 0.9.10 0.8.13, 0.9.10
  10. High CVE-2026-41674 < 0.8.13, >= 0.9.0, < 0.9.10 0.8.13, 0.9.10
  11. High CVE-2026-41675 < 0.8.13, >= 0.9.0, < 0.9.10 0.8.13, 0.9.10
  12. High CVE-2026-41672 < 0.8.13, >= 0.9.0, < 0.9.10 0.8.13, 0.9.10
  13. High · 7.5 CVE-2026-34601 < 0.8.12, >= 0.9.0, < 0.9.9 0.8.12, 0.9.9
  14. Critical · 9.8 CVE-2022-39353 < 0.7.7, >= 0.8.0, < 0.8.4, >= 0.9.0-beta.1, < 0.9.0-beta.4 0.7.7, 0.8.4, 0.9.0-beta.4

Know which vulnerabilities affect your application

With the DockRay integration and software inventory enabled, DockRay checks reported component versions against known vulnerabilities and notifies you about confirmed matches. Coverage depends on the supported technology and available advisory data.

Connect your application
DockRay newsletter

Application security in your inbox

Important vulnerabilities, actionable guidance and practical advice for your applications. Choose your technologies and follow what matters to your project.

  • A daily digest of new vulnerabilities, critical ones included.
  • Notifications about important updates.
  • Practical advice on application security and monitoring.

Leave empty to receive updates for every technology.

Free to subscribe. Unsubscribe with one click in any email.