GitHub Advisoryadvisory · npm
GHSA-6mj3-qw4j-hgrw xmldom: HTML raw-text closing-tag case mismatch causes output amplification
HIGH
Official page ↗
- Aliases
- CVE-2026-83612
- Ecosystem
- npm
- Packages
- @xmldom/xmldom
- Severity
- High
- 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
- Weaknesses
- CWE-178, CWE-400
- Reviewed
- 2026-09-08
Affected packages
• npm @xmldom/xmldom: >= 0.9.0-beta.1, < 0.9.12 → fixed in 0.9.12
Description
Summary
In HTML mode (`text/html`), a raw-text element (`script`, `style`, `textarea`, `title`) whose closing
tag differs in case from its opening tag (e.g. `` for ``) is mishandled by the
parser, producing quadratic (O(n²)) output growth — a small crafted document parses and serializes
into output orders of magnitude larger, exhausting CPU and memory. A modest input of tens of KB can
therefore cause a denial of service in any service that parses untrusted HTML with xmldom. Only HTML
mode is affected.
Details
The parser calls `parseHtmlSpecialContent` for each raw-text element in HTML mode, matched via
`isHTMLRawTextElement` / `isHTMLEscapableRawTextElement` (so all four types — `script`, `style`,
`textarea`, `title` — are in scope). It searches for the element's closing tag with
`source.indexOf('', elStartEnd)`, a byte-for-byte case-sensitive match. A
mixed-case closing tag never matches, so the search returns `-1`, and the following
`source.substring(elStartEnd + 1, -1)` extracts text backwards from the start of the document
instead of the element's content. The function then returns `-1` to the parse loop, which cannot
advance normally and falls back to character-by-character reprocessing. Every raw-text element
re-captures all source text preceding it, so output grows as O(n²) in the number of such elements.
Root Cause
1. Case-sensitive close-tag search (`lib/sax.js:549`): `source.indexOf('',
elStartEnd)` does not fold case, contrary to the WHATWG HTML RAWTEXT end-tag-name rule.
2. Unguarded `-1` (`lib/sax.js:550`): `source.substring(elStartEnd + 1, elEndStart)` runs even
when `elEndStart === -1`, extracting text backwards from position 0.
3. Unstable progression (`lib/sax.js:556`): the function returns `elEndStart` (`-1`), driving
repeated character-by-character fallback in the parse loop.
Affected Versions
Only the `0.9.x` line is affected — the amplification was introduced in `0.9.0-beta.1` when
`parseHtmlSpecialContent` was refactored, and remains through `0.9.11`. The `0.8.x` line is not
affected: its older `parseHtmlSpecialContent` does not amplify, despite sharing the same
case-sensitive `indexOf`.
Proof of Concept
```js
const { DOMParser, XMLSerializer } = require('@xmldom/xmldom');
const n = 1000;
const payload = '' + 'x'.repeat(n) + '';
const doc = new DOMParser().parseFromString(payload, 'text/html');
const out = new XMLSerializer().serializeToString(doc);
console.log(payload.length, out.length, (out.length / payload.length).toFixed(1) + 'x');
// 18026 9037063 501.3x — an 18 KB input yields ~9 MB of output
```
Output size grows quadratically with the number of case-mismatched raw-text elements:
```
Repeats | Input len | Output len | Ratio
1 | 44 | 109 | 2.5x
100 | 1826 | 93763 | 51.3x
500 | 9026 | 2268563 | 251.3x
1000 | 18026 | 9037063 | 501.3x
2000 | 36026 | 36074063 | 1001.3x
```
Proof of Concept from @KarimTantawey (tested with `script`); the same amplification occurs for `style`,
`textarea`, and `title`.
Impact
Small attacker payloads can force disproportionate CPU and memory usage in services that
parse and serialize untrusted HTML via xmldom. The quadratic growth means a modest-sized input
(tens of kilobytes) can produce output in the tens or hundreds of megabytes, potentially
exhausting memory or causing timeouts.
The attack only requires HTML mode (`text/html` MIME type) and mixed-case closing tags for
any of the four raw-text element types. No special configuration or error handler setup is needed.
Severity note
The CVSS 4.0 vector scores availability only (`VA:H`, with `VC:N/VI:N`): the flaw neither discloses
nor corrupts data, but a small untrusted HTML input (tens of KB) can force output and memory in the
tens to hundreds of MB, enough to exhaust a service's heap or stall its event loop. It is reachable
with no authentication, configuration, or error-handler setup — only that the application parses
untrusted `text/html` and serializes the result.
Fix Applied
The raw-text closing tag is now matched case-insensitively in HTML raw-text mode (per the WHATWG HTML
RAWTEXT end-tag rule),
and a missing closing tag is handled explicitly, removing the quadratic output amplification. Output
for well-formed input is unchanged. Non-breaking; 0.9.x-only.
Related
Alias · CVE (1)
- CVE-2026-83612 xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) DOMParser and XMLSerializer module. vulnerability
References
- https://github.com/xmldom/xmldom/security/advisories/GHSA-6mj3-qw4j-hgrw web
- https://nvd.nist.gov/vuln/detail/CVE-2026-83612 advisory
- https://github.com/xmldom/xmldom/pull/1071 web
- https://github.com/xmldom/xmldom/commit/7ced40c06c28d151e996a97045018c3559ae4707 web
- https://github.com/xmldom/xmldom package
- https://github.com/xmldom/xmldom/releases/tag/0.9.12 web