CVE-2026-83606: xmldom PI grammar regex ReDoS Quadratic Backtracking On Unterminated Processing Vulnerability

HIGH
Advisory Identifier: GHSA-g53g-w8rj-fmg7
CWE-400 CWE-1333
Published
Advisory Summary

CVE-2026-83606 is a HIGH quadratic backtracking on unterminated processing vulnerability (CWE-400: Uncontrolled Resource Consumption) affecting xmldom PI grammar regex ReDoS libraries in the npm ecosystem. The issue involves quadratic backtracking on unterminated processing instructions. Remediation is available by upgrading affected packages to version 0.9.11.

CVE-2026-83606 at a Glance

CVE Identifier CVE-2026-83606
GHSA Identifier GHSA-g53g-w8rj-fmg7
Severity HIGH
CVSS v3.1 Base Score N/A
CWE Classification CWE-400: Uncontrolled Resource Consumption
Affected Product xmldom PI grammar regex ReDoS
Package Ecosystem npm
Published Date
Last Updated

How to Fix CVE-2026-83606

To remediate this vulnerability, upgrade all affected xmldom PI grammar regex ReDoS dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

@xmldom/xmldom fixed: 0.9.11
npm install @xmldom/xmldom@0.9.11
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls @xmldom/xmldom

How to Check if You Are Affected

To determine whether your Node.js application is affected by CVE-2026-83606, inspect your project dependencies using npm:

npm ls @xmldom/xmldom

Analyze the terminal output to locate any version of xmldom PI grammar regex ReDoS in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.9.11), your application is affected.

Note: The npm ls @xmldom/xmldom command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-400: Uncontrolled Resource Consumption, where the product does not properly control the allocation and maintenance of a limited resource. In xmldom PI grammar regex ReDoS, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## 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.js` line 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-L691 - `parseProcessingInstruction` — 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 ```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](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.

CVE-2026-83606 Affected xmldom PI grammar regex ReDoS Versions

1 component(s)
Fixed Versions:
0.9.11
Product / Package Ecosystem Affected Versions Fixed Version
@xmldom/xmldom
npm
≥ 0.9.0-beta.9
< 0.9.11
0.9.11

References

Frequently Asked Questions

What is CVE-2026-83606?
CVE-2026-83606 is a security vulnerability in xmldom PI grammar regex ReDoS involving quadratic backtracking on unterminated processing instructions under CWE-400: Uncontrolled Resource Consumption.
Which xmldom PI grammar regex ReDoS versions are affected?
Affected versions include releases prior to 0.9.11 across affected packages: @xmldom/xmldom.
What version fixes CVE-2026-83606?
Upgrading to 0.9.11 (or the latest patched release for your respective branch) resolves the vulnerability.
Is @xmldom/xmldom affected?
Yes, @xmldom/xmldom is affected in releases prior to 0.9.11. Upgrade your dependency to 0.9.11 or later.
What CWE is associated with CVE-2026-83606?
CVE-2026-83606 is classified under CWE-400: Uncontrolled Resource Consumption (The product does not properly control the allocation and maintenance of a limited resource.).
How can I check npm dependencies for CVE-2026-83606?
Run the dependency tree command: 'npm ls @xmldom/xmldom'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-g53g-w8rj-fmg7",
  "modified" : "2026-09-08T20:31:50Z",
  "published" : "2026-09-08T20:31:50Z",
  "aliases" : [ "CVE-2026-83606" ],
  "summary" : "xmldom PI grammar regex ReDoS: quadratic backtracking on unterminated processing instructions",
  "details" : "## Summary\n\n`@xmldom/xmldom`'s processing-instruction (PI) grammar regex exhibits quadratic-time backtracking\n(ReDoS) when parsing an **unterminated** processing instruction. A single small XML document\ncontaining `<?` + a target + a long run of whitespace and no closing `?>` forces the regular\nexpression engine into O(n²) work, stalling the Node.js event loop. The input is parsed with\n`DOMParser.parseFromString` under **default options**, so it is reachable from unauthenticated,\nnetwork-delivered XML (SOAP/SAML, webhooks, uploads, XML APIs).\n\n## Details\n\nThe PI production in `lib/grammar.js` compiles (flags `mu`) to:\n\n```\n^<\\?(NameChars)(?:[\\x20\\x09\\x0D\\x0A]+([Char]*?))?\\?>\n                     ^^^ S+ greedy       ^^^ Char*? lazy\n```\n\n- `lib/grammar.js` line 261: https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/grammar.js#L261\n\nIn the optional tail `(?:S+(Char*?))?`, both the greedy separator `S+` and the lazy data `Char*?`\nmatch XML whitespace. When the required trailing `?>` is absent, the engine must ultimately fail —\nbut first it tries every partition of the whitespace run between `S+` and `Char*?`, which is O(n²)\nin the length of the trailing whitespace.\n\nThe regex is executed against the **entire remaining source string** in two places in `lib/sax.js`,\nso the whole whitespace tail is scanned:\n\n- `parsePI` — https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/sax.js#L680-L691\n- `parseProcessingInstruction` — https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/sax.js#L862-L879\n\n## Affected Versions\n\nOnly the `0.9.x` line is affected. `lib/grammar.js` (and this PI regex) was introduced in\ncommit `726b471` (\"fix!: preserve DOCTYPE internal subset (#498)\"), first released in\n**0.9.0-beta.9**, and is unchanged through **0.9.10**.\n\nThe `0.8.x` line (≤ 0.8.13) and the unscoped `xmldom` package (≤ 0.6.0) parse PIs via a different\ncode path bounded by `indexOf('?>')` — they do **not** contain this regex and are **not affected**\nby this issue. (They were not separately tested for a *different* PI ReDoS; the scope here is the\nspecific `grammar.js` regex.)\n\n| Line | PI code path | Affected? |\n|---|---|---|\n| `0.9.x` (0.9.0-beta.9 … 0.9.10) | `grammar.js` `PI` regex over full remaining source | **Yes** |\n| `0.8.x` (≤ 0.8.13) | `parseInstruction`, bounded by `indexOf('?>')` | No |\n| unscoped `xmldom` (≤ 0.6.0) | older `indexOf('?>')`-bounded parsing | No |\n\n## Proof of Concept\n\n```js\nconst { DOMParser } = require('@xmldom/xmldom');\nconst n = 32 * 1024;\nconst payload = '<a><?p' + ' '.repeat(n); // unterminated PI, no `?>`\nconsole.time('parse');\nnew DOMParser().parseFromString(payload, 'text/xml');\nconsole.timeEnd('parse');\n```\n\nMeasured (Node 18), trailing whitespace after `<?p`, no `?>` — time quadruples per doubling of\ninput length (canonical O(n²)):\n\n| Trailing whitespace | `g.PI.exec` | `parseFromString` |\n|---|---|---|\n| 2 KB  | 4.4 ms    | 5.1 ms   |\n| 4 KB  | 16.9 ms   | 17.0 ms  |\n| 8 KB  | 111.4 ms  | 66.3 ms  |\n| 16 KB | 263.8 ms  | 336.5 ms |\n| 32 KB | 1073.1 ms | —        |\n\n## Impact\n\nAvailability only: a single parse of a small crafted document blocks the Node.js event loop for the\nduration of the quadratic scan (≈1 s at 32 KB; multi-second with larger inputs). No memory blow-up,\nno data exposure, no integrity impact. Because XML is routinely accepted from untrusted sources and\nparsed with default options, one request can stall a server.\n\n## Fix Applied\n\nFixed in `@xmldom/xmldom` **0.9.11** (`0.9.x`-only; the `0.8.x` LTS line and the\nunscoped `xmldom` package use a different, bounded PI code path and are not affected).\n\nPR [#1039](https://github.com/xmldom/xmldom/pull/1039) inserts a fixed-width negative lookahead\n`(?!\\s)` immediately after the greedy `S+`, so the separator can no longer hand whitespace back to\nthe lazy data group:\n\n```\n- var PI = reg(/^<\\?/, '(', Name, ')', regg(S, '(', Char, '*?)'), '?', /\\?>/);\n+ var PI = reg(/^<\\?/, '(', Name, ')', regg(S, '(?!', _SChar, ')(', Char, '*?)'), '?', /\\?>/);\n```\n\nThe change is correct, minimal, and behavior-preserving: it produces identical `[target, data]`\ncaptures on all valid PIs tested (incl. whitespace-heavy, tab/newline, empty-data, and xml-decl\ncases) and removes the backtracking blow-up (linear, ~0.4 ms at 128 KB after the fix). The lookahead\nis fixed-width and cannot itself backtrack — a strict improvement with no new parsing risk.\n\n## Severity note\n\nThe complexity is **quadratic**, not exponential, so a multi-second stall requires\ntens-to-hundreds of KB of input. `VA:H` reflects that xmldom applies **no input-size limit** and the\npath runs on default-options parsing, so a single unbounded parse can fully stall the event loop.",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "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"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "@xmldom/xmldom"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0.9.0-beta.9"
      }, {
        "fixed" : "0.9.11"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 0.9.10"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/xmldom/xmldom/security/advisories/GHSA-g53g-w8rj-fmg7"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-83606"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/xmldom/xmldom/pull/1039"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/xmldom/xmldom/commit/73df6b8bdbd86f904b9e8c3ab9c49aa54ef2802e"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/xmldom/xmldom"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/xmldom/xmldom/releases/tag/0.9.11"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-1333", "CWE-400" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-09-08T20:31:50Z",
    "nvd_published_at" : "2026-09-01T15:17:38Z"
  }
}