CVE-2026-34601: xmldom XML Injection Vulnerability

HIGH
Advisory Identifier: GHSA-wh4c-j3r5-mjhp
CVSS 7.5
CWE-91
Published
Advisory Summary

CVE-2026-34601 is a HIGH (CVSS 7.5) xml injection vulnerability (CWE-91: XML Injection (aka Blind XPath Injection)) affecting xmldom libraries in the npm ecosystem. The issue involves xml injection via unsafe cdata serialization allows attacker-controlled markup insertion. Remediation is available by upgrading affected packages to version 0.8.12, 0.9.9.

CVE-2026-34601 at a Glance

CVE Identifier CVE-2026-34601
GHSA Identifier GHSA-wh4c-j3r5-mjhp
Severity HIGH
CVSS v3.1 Base Score 7.5
CWE Classification CWE-91: XML Injection (aka Blind XPath Injection)
Affected Product xmldom
Package Ecosystem npm
Published Date
Last Updated

Security Impact

CVSS: 7.5

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity High (I:H)
Availability None (A:N)

How to Fix CVE-2026-34601

To remediate this vulnerability, upgrade all affected xmldom 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 fixed: Latest secure release
npm install xmldom@Latest secure release
@xmldom/xmldom fixed: 0.8.12
npm install @xmldom/xmldom@0.8.12
@xmldom/xmldom fixed: 0.9.9
npm install @xmldom/xmldom@0.9.9
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

How to Check if You Are Affected

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

npm ls xmldom

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

Note: The npm ls 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-91: XML Injection (aka Blind XPath Injection), where the product does not properly neutralize special elements that are used in xml, allowing attackers to modify the syntax, content, or commands of the xml before it is processed by an end system. In xmldom, 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` allows attacker-controlled strings containing the CDATA terminator `]]>` to be inserted into a `CDATASection` node. During serialization, `XMLSerializer` emitted the CDATA content verbatim without rejecting or safely splitting the terminator. As a result, data intended to remain text-only became **active XML markup** in the serialized output, enabling XML structure injection and downstream business-logic manipulation. The sequence `]]>` is not allowed inside CDATA content and must be rejected or safely handled during serialization. ([MDN Web Docs](https://developer.mozilla.org/)) ### Attack surface `Document.createCDATASection(data)` is the most direct entry point, but it is not the only one. The WHATWG DOM spec intentionally does not validate `]]>` in mutation methods — only `createCDATASection` carries that guard. The following paths therefore also allow `]]>` to enter a CDATASection node and reach the serializer: - `CharacterData.appendData()` - `CharacterData.replaceData()` - `CharacterData.insertData()` - Direct assignment to `.data` - Direct assignment to `.textContent` (Note: assigning to `.nodeValue` does **not** update `.data` in this implementation — the serializer reads `.data` directly — so `.nodeValue` is not an exploitable path.) ### Parse path Parsing XML that contains a CDATA section is **not** affected. The SAX parser's non-greedy `CDSect` regex stops at the first `]]>`, so parsed CDATA data never contains the terminator. --- ## Impact If an application uses `xmldom` to generate "trusted" XML documents that embed **untrusted user input** inside CDATA (a common pattern in exports, feeds, SOAP/XML integrations, etc.), an attacker can inject additional XML elements/attributes into the generated document. This can lead to: - Integrity violation of generated XML documents. - Business-logic injection in downstream consumers (e.g., injecting `<approved>true</approved>`, `<role>admin</role>`, workflow flags, or other security-relevant elements). - Unexpected privilege/workflow decisions if downstream logic assumes injected nodes cannot appear. This issue does **not** require malformed parsers or browser behavior; it is caused by serialization producing attacker-influenced XML markup. --- ## Root Cause (with file + line numbers) **File:** `lib/dom.js` ### 1. No validation in `createCDATASection` `createCDATASection: function (data)` accepts any string and appends it directly. - **Lines 2216–2221** (0.9.8) ### 2. Unsafe CDATA serialization Serializer prints CDATA sections as: ``` <![CDATA[ + node.data + ]]> ``` without handling `]]>` in the data. - **Lines 2919–2920** (0.9.8) Because CDATA content is emitted verbatim, an embedded `]]>` closes the CDATA section early and the remainder of the attacker-controlled payload is interpreted as markup in the serialized XML. --- ## Proof of Concept — Fix A: `createCDATASection` now throws On patched versions, passing `]]>` directly to `createCDATASection` throws `InvalidCharacterError` instead of silently accepting the payload: ```js const { DOMImplementation } = require('./lib'); const doc = new DOMImplementation().createDocument(null, 'root', null); try { doc.createCDATASection('SAFE]]><injected attr="pwn"/>'); console.log('VULNERABLE — no error thrown'); } catch (e) { console.log('FIXED — threw:', e.name); // InvalidCharacterError } ``` Expected output on patched versions: ``` FIXED — threw: InvalidCharacterError ``` --- ## Proof of Concept — Fix B: mutation vector now safe On patched versions, injecting `]]>` via a mutation method (`appendData`, `replaceData`, `.data =`, `.textContent =`) no longer produces injectable output. The serializer splits the terminator so the result round-trips as safe text: ```js const { DOMImplementation, XMLSerializer } = require('./lib'); const { DOMParser } = require('./lib'); const doc = new DOMImplementation().createDocument(null, 'root', null); // Start with safe data, then mutate to include the terminator const cdata = doc.createCDATASection('safe'); doc.documentElement.appendChild(cdata); cdata.appendData(']]><injected attr="pwn"/><more>TEXT</more><![CDATA['); const out = new XMLSerializer().serializeToString(doc); console.log('Serialized:', out); const reparsed = new DOMParser().parseFromString(out, 'text/xml'); const injected = reparsed.getElementsByTagName('injected').length > 0; console.log('Injected element found in reparsed doc:', injected); // VULNERABLE: true | FIXED: false ``` Expected output on patched versions: ``` Serialized: <root><![CDATA[safe]]]]><![CDATA[><injected attr="pwn"/><more>TEXT</more><![CDATA[]]></root> Injected element found in reparsed doc: false ``` --- ## Fix Applied Both mitigations were implemented: ### Option A — Strict/spec-aligned: reject `]]>` in `createCDATASection()` `Document.createCDATASection(data)` now throws `InvalidCharacterError` (per the [WHATWG DOM spec](https://dom.spec.whatwg.org/#dom-document-createcdatasection)) when `data` contains `]]>`. This closes the direct entry point. Code that previously passed a string containing `]]>` to `createCDATASection` and relied on the silent/unsafe behaviour will now receive `InvalidCharacterError`. Use a mutation method such as `appendData` if you intentionally need `]]>` in a CDATASection node's data (the serializer split in Option B will keep the output safe). ### Option B — Defensive serialization: split the terminator during serialization `XMLSerializer` now replaces every occurrence of `]]>` in CDATA section data with the split sequence `]]]]><![CDATA[>` before emitting. This closes all mutation-vector paths that Option A alone cannot guard, and means the serialized output is always well-formed XML regardless of how `]]>` entered the node. ## Update — 2026-04-xx (0.9.10 / 0.8.13) ### `splitCDATASections` is deprecated The CDATA split behavior introduced as Option B of this fix (replacing `]]>` with`]]]]><![CDATA[>` during serialization) is **deprecated** as of 0.9.10 / 0.8.13. This release introduces a `requireWellFormed` option on `XMLSerializer.serializeToString()`. When `{ requireWellFormed: true }` is passed as the second argument, the serializer throws `InvalidStateError` if CDATA section data contains `]]>` — this is the spec-aligned behavior (W3C DOM Parsing and Serialization, `require well-formed` flag) and the recommended migration path going forward. The split behavior is now controlled by an explicit `splitCDATASections` option (default `true`, preserving the current behavior). The three serialization behaviors are: | `requireWellFormed` | `splitCDATASections` | Behavior ||---|---|---|| `false` (default) | `true` (default) | Split `]]>` → `]]]]><![CDATA[>` (current behavior, deprecated) || `true` | — (ignored) | Throw `InvalidStateError` — spec-aligned, recommended |\ `false` | `false` | Emit verbatim — same as pre-0.9.9 behavior | `requireWellFormed: true` takes precedence: the split path is unreachable when it is set. ### Migration Replace any reliance on the default split behavior with an explicit opt-in: ```js// Before (implicit split, deprecated): const xml = new XMLSerializer().serializeToString(doc); // After (explicit guard, spec-aligned): const xml = new XMLSerializer().serializeToString(doc, { requireWellFormed: true }); // Throws InvalidStateError if any CDATASection contains ']]>' ``` ### Removal timeline Both the `splitCDATASections` option and the underlying `]]>` → `]]]]><![CDATA[>` split mechanics will be removed in the next breaking (`0.10.0`) release. After removal, the only behaviors will be verbatim (default) and `requireWellFormed: true` (throws). Removal is tracked in [xmldom/xmldom#999](https://github.com/xmldom/xmldom/issues/999).

CVE-2026-34601 Affected xmldom Versions

3 component(s)
Fixed Versions:
0.8.120.9.9
Product / Package Ecosystem Affected Versions Fixed Version
xmldom
npm
≤ 0.6.0
@xmldom/xmldom
npm
< 0.8.12
0.8.12
@xmldom/xmldom
npm
≥ 0.9.0
< 0.9.9
0.9.9

References

Frequently Asked Questions

What is CVE-2026-34601?
CVE-2026-34601 is a security vulnerability in xmldom involving XML injection via unsafe CDATA serialization allows attacker-controlled markup insertion under CWE-91: XML Injection (aka Blind XPath Injection).
Which xmldom versions are affected?
Affected versions include releases prior to 0.8.12, 0.9.9 across affected packages: xmldom, @xmldom/xmldom.
What version fixes CVE-2026-34601?
Upgrading to 0.8.12, 0.9.9 (or the latest patched release for your respective branch) resolves the vulnerability.
Is xmldom affected?
Yes, xmldom is affected in releases prior to Latest secure release. Upgrade your dependency to Latest secure release or later.
Is @xmldom/xmldom affected?
Yes, @xmldom/xmldom is affected in releases prior to 0.8.12, 0.9.9. Upgrade your dependency to 0.8.12, 0.9.9 or later.
What is the CVSS score of CVE-2026-34601?
CVE-2026-34601 has a CVSS v3.1 base score of 7.5 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N.
What CWE is associated with CVE-2026-34601?
CVE-2026-34601 is classified under CWE-91: XML Injection (aka Blind XPath Injection) (The product does not properly neutralize special elements that are used in XML, allowing attackers to modify the syntax, content, or commands of the XML before it is processed by an end system.).
How can I check npm dependencies for CVE-2026-34601?
Run the dependency tree command: 'npm ls 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-wh4c-j3r5-mjhp",
  "modified" : "2026-04-24T23:17:44Z",
  "published" : "2026-04-01T00:19:06Z",
  "aliases" : [ "CVE-2026-34601" ],
  "summary" : "xmldom: XML injection via unsafe CDATA serialization allows attacker-controlled markup insertion",
  "details" : "## Summary\n\n`@xmldom/xmldom` allows attacker-controlled strings containing the CDATA terminator `]]>` to be inserted into a `CDATASection` node. During serialization, `XMLSerializer` emitted the CDATA content verbatim without rejecting or safely splitting the terminator. As a result, data intended to remain text-only became **active XML markup** in the serialized output, enabling XML structure\ninjection and downstream business-logic manipulation.\n\nThe sequence `]]>` is not allowed inside CDATA content and must be rejected or safely handled during serialization. ([MDN Web Docs](https://developer.mozilla.org/))\n\n### Attack surface\n\n`Document.createCDATASection(data)` is the most direct entry point, but it is not the only one. The WHATWG DOM spec intentionally does not validate `]]>` in mutation methods — only `createCDATASection` carries that guard. The following paths therefore also allow `]]>` to enter a CDATASection node and reach the serializer:\n\n- `CharacterData.appendData()`\n- `CharacterData.replaceData()`\n- `CharacterData.insertData()`\n- Direct assignment to `.data`\n- Direct assignment to `.textContent`\n\n(Note: assigning to `.nodeValue` does **not** update `.data` in this implementation — the serializer reads `.data` directly — so `.nodeValue` is not an exploitable path.)\n\n### Parse path\n\nParsing XML that contains a CDATA section is **not** affected. The SAX parser's non-greedy `CDSect` regex stops at the first `]]>`, so parsed CDATA data never contains the terminator.\n\n---\n\n## Impact\n\nIf an application uses `xmldom` to generate \"trusted\" XML documents that embed **untrusted user input** inside CDATA (a common pattern in exports, feeds, SOAP/XML integrations, etc.), an attacker can inject additional XML elements/attributes into the generated document.\n\nThis can lead to:\n\n- Integrity violation of generated XML documents.\n- Business-logic injection in downstream consumers (e.g., injecting `<approved>true</approved>`,  `<role>admin</role>`, workflow flags, or other security-relevant elements).\n- Unexpected privilege/workflow decisions if downstream logic assumes injected nodes cannot appear.\n\nThis issue does **not** require malformed parsers or browser behavior; it is caused by serialization producing attacker-influenced XML markup.\n\n---\n\n## Root Cause (with file + line numbers)\n\n**File:** `lib/dom.js`\n\n### 1. No validation in `createCDATASection`\n\n`createCDATASection: function (data)` accepts any string and appends it directly.\n\n- **Lines 2216–2221** (0.9.8)\n\n### 2. Unsafe CDATA serialization\n\nSerializer prints CDATA sections as:\n\n```\n<![CDATA[ + node.data + ]]>\n```\n\nwithout handling `]]>` in the data.\n\n- **Lines 2919–2920** (0.9.8)\n\nBecause CDATA content is emitted verbatim, an embedded `]]>` closes the CDATA section early and the remainder of the attacker-controlled payload is interpreted as markup in the serialized XML.\n\n---\n\n## Proof of Concept — Fix A: `createCDATASection` now throws\n\nOn patched versions, passing `]]>` directly to `createCDATASection` throws `InvalidCharacterError` instead of silently accepting the payload:\n\n```js\nconst { DOMImplementation } = require('./lib');\n\nconst doc = new DOMImplementation().createDocument(null, 'root', null);\ntry {\n  doc.createCDATASection('SAFE]]><injected attr=\"pwn\"/>');\n  console.log('VULNERABLE — no error thrown');\n} catch (e) {\n  console.log('FIXED — threw:', e.name); // InvalidCharacterError\n}\n```\n\nExpected output on patched versions:\n\n```\nFIXED — threw: InvalidCharacterError\n```\n\n---\n\n## Proof of Concept — Fix B: mutation vector now safe\n\nOn patched versions, injecting `]]>` via a mutation method (`appendData`, `replaceData`, `.data =`, `.textContent =`) no longer produces injectable output. The serializer splits the terminator so the result round-trips as safe text:\n\n```js\nconst { DOMImplementation, XMLSerializer } = require('./lib');\nconst { DOMParser } = require('./lib');\n\nconst doc = new DOMImplementation().createDocument(null, 'root', null);\n\n// Start with safe data, then mutate to include the terminator\nconst cdata = doc.createCDATASection('safe');\ndoc.documentElement.appendChild(cdata);\ncdata.appendData(']]><injected attr=\"pwn\"/><more>TEXT</more><![CDATA[');\n\nconst out = new XMLSerializer().serializeToString(doc);\nconsole.log('Serialized:', out);\n\nconst reparsed = new DOMParser().parseFromString(out, 'text/xml');\nconst injected = reparsed.getElementsByTagName('injected').length > 0;\nconsole.log('Injected element found in reparsed doc:', injected);\n// VULNERABLE: true  |  FIXED: false\n```\n\nExpected output on patched versions:\n\n```\nSerialized: <root><![CDATA[safe]]]]><![CDATA[><injected attr=\"pwn\"/><more>TEXT</more><![CDATA[]]></root>\nInjected element found in reparsed doc: false\n```\n\n---\n\n## Fix Applied\n\nBoth mitigations were implemented:\n\n### Option A — Strict/spec-aligned: reject `]]>` in `createCDATASection()`\n\n`Document.createCDATASection(data)` now throws `InvalidCharacterError` (per the [WHATWG DOM spec](https://dom.spec.whatwg.org/#dom-document-createcdatasection)) when `data` contains `]]>`. This closes the direct entry point.\n\nCode that previously passed a string containing `]]>` to `createCDATASection` and relied on the silent/unsafe behaviour will now receive `InvalidCharacterError`. Use a mutation method such as `appendData` if you intentionally need `]]>` in a CDATASection node's data (the serializer split in Option B will keep the output safe).\n\n### Option B — Defensive serialization: split the terminator during serialization\n\n`XMLSerializer` now replaces every occurrence of `]]>` in CDATA section data with the split sequence `]]]]><![CDATA[>` before emitting. This closes all mutation-vector paths that Option A alone cannot guard, and means the serialized output is always well-formed XML regardless of how `]]>` entered the node.\n\n## Update — 2026-04-xx (0.9.10 / 0.8.13)\n\n### `splitCDATASections` is deprecated\n\nThe CDATA split behavior introduced as Option B of this fix (replacing `]]>` with`]]]]><![CDATA[>` during serialization) is **deprecated** as of 0.9.10 / 0.8.13.\n\nThis release introduces a `requireWellFormed` option on `XMLSerializer.serializeToString()`. When `{ requireWellFormed: true }` is passed as the second argument, the serializer throws `InvalidStateError` if CDATA section data contains `]]>` — this is the spec-aligned behavior (W3C DOM Parsing and Serialization, `require well-formed` flag) and the recommended migration path going forward.\nThe split behavior is now controlled by an explicit `splitCDATASections` option (default `true`, preserving the current behavior). The three serialization behaviors are:\n| `requireWellFormed` | `splitCDATASections` | Behavior ||---|---|---|| `false` (default) | `true` (default) | Split `]]>` → `]]]]><![CDATA[>` (current behavior, deprecated) || `true` | — (ignored) | Throw `InvalidStateError` — spec-aligned, recommended |\\ `false` | `false` | Emit verbatim — same as pre-0.9.9 behavior |\n\n`requireWellFormed: true` takes precedence: the split path is unreachable when it is set.\n\n### Migration\nReplace any reliance on the default split behavior with an explicit opt-in:\n```js// Before (implicit split, deprecated): const xml = new XMLSerializer().serializeToString(doc);\n\n// After (explicit guard, spec-aligned): const xml = new XMLSerializer().serializeToString(doc, { requireWellFormed: true }); // Throws InvalidStateError if any CDATASection contains ']]>' ```\n\n### Removal timeline\nBoth the `splitCDATASections` option and the underlying `]]>` → `]]]]><![CDATA[>` split mechanics will be removed in the next breaking (`0.10.0`) release. After removal, the only behaviors will be verbatim (default) and `requireWellFormed: true` (throws).\n\nRemoval is tracked in [xmldom/xmldom#999](https://github.com/xmldom/xmldom/issues/999).",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "xmldom"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "last_affected" : "0.6.0"
      } ]
    } ]
  }, {
    "package" : {
      "ecosystem" : "npm",
      "name" : "@xmldom/xmldom"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "0.8.12"
      } ]
    } ]
  }, {
    "package" : {
      "ecosystem" : "npm",
      "name" : "@xmldom/xmldom"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0.9.0"
      }, {
        "fixed" : "0.9.9"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/xmldom/xmldom/security/advisories/GHSA-wh4c-j3r5-mjhp"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-34601"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/xmldom/xmldom/commit/2b852e836ab86dbbd6cbaf0537f584dd0b5ac184"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/xmldom/xmldom"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/xmldom/xmldom/releases/tag/0.8.12"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/xmldom/xmldom/releases/tag/0.9.9"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-91" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-04-01T00:19:06Z",
    "nvd_published_at" : "2026-04-02T18:16:31Z"
  }
}