VYPR
High severity7.5NVD Advisory· Published Apr 2, 2026· Updated Apr 16, 2026

CVE-2026-34601

CVE-2026-34601

Description

xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) DOMParser and XMLSerializer module. In xmldom versions 0.6.0 and prior and @xmldom/xmldom prior to versions 0.8.12 and 0.9.9, 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. This issue has been patched in xmldom version 0.6.0 and @xmldom/xmldom versions 0.8.12 and 0.9.9.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
xmldomnpm
<= 0.6.0
@xmldom/xmldomnpm
< 0.8.120.8.12
@xmldom/xmldomnpm
>= 0.9.0, < 0.9.90.9.9

Affected products

1

Patches

1
2b852e836ab8

fix: XML injection via unsafe CDATA serialization (GHSA-wh4c-j3r5-mjhp) (#969)

https://github.com/xmldom/xmldomChristian BewernitzMar 29, 2026via ghsa
6 files changed · +203 7
  • CHANGELOG.md+32 0 modified
    @@ -4,6 +4,38 @@ All notable changes to this project will be documented in this file.
     
     This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
     
    +## [0.9.9](https://github.com/xmldom/xmldom/compare/0.9.8...0.9.9)
    +
    +### Fixed
    +
    +- Security: `createCDATASection` now throws `InvalidCharacterError` when `data` contains `"]]>"`, as required by the [WHATWG DOM spec](https://dom.spec.whatwg.org/#dom-document-createcdatasection). [`GHSA-wh4c-j3r5-mjhp`](https://github.com/xmldom/xmldom/security/advisories/GHSA-wh4c-j3r5-mjhp)
    +- Security: `XMLSerializer` now splits CDATASection nodes whose data contains `"]]>"` into adjacent CDATA sections at serialization time, preventing XML injection via mutation methods (`appendData`, `replaceData`, `.data =`, `.textContent =`). [`GHSA-wh4c-j3r5-mjhp`](https://github.com/xmldom/xmldom/security/advisories/GHSA-wh4c-j3r5-mjhp)
    +
    +Code that passes a string containing `"]]>"` to `createCDATASection` and relied on the previously unsafe behavior will now receive `InvalidCharacterError`. Use a mutation method such as `appendData` if you intentionally need `"]]>"` in a CDATASection node's data.
    +
    +Thank you, [@thesmartshadow](https://github.com/thesmartshadow), for your contributions
    +
    +
    +## [0.8.12](https://github.com/xmldom/xmldom/compare/0.8.11...0.8.12)
    +
    +### Fixed
    +
    +- Security: `createCDATASection` now throws `InvalidCharacterError` when `data` contains `"]]>"`, as required by the [WHATWG DOM spec](https://dom.spec.whatwg.org/#dom-document-createcdatasection). [`GHSA-wh4c-j3r5-mjhp`](https://github.com/xmldom/xmldom/security/advisories/GHSA-wh4c-j3r5-mjhp)
    +- Security: `XMLSerializer` now splits CDATASection nodes whose data contains `"]]>"` into adjacent CDATA sections at serialization time, preventing XML injection via mutation methods (`appendData`, `replaceData`, `.data =`, `.textContent =`). [`GHSA-wh4c-j3r5-mjhp`](https://github.com/xmldom/xmldom/security/advisories/GHSA-wh4c-j3r5-mjhp)
    +
    +Code that passes a string containing `"]]>"` to `createCDATASection` and relied on the previously unsafe behavior will now receive `InvalidCharacterError`. Use a mutation method such as `appendData` if you intentionally need `"]]>"` in a CDATASection node's data.
    +
    +Thank you, [@thesmartshadow](https://github.com/thesmartshadow), for your contributions
    +
    +
    +## [0.8.11](https://github.com/xmldom/xmldom/compare/0.8.10...0.8.11)
    +
    +### Fixed
    +
    +- update `ownerDocument` when moving nodes between documents [`#933`](https://github.com/xmldom/xmldom/pull/933) / [`#932`](https://github.com/xmldom/xmldom/issues/932)
    +
    +Thank you, [@shunkica](https://github.com/shunkica), for your contributions
    +
     ## [0.9.8](https://github.com/xmldom/xmldom/compare/0.9.8...0.9.7)
     
     ### Fixed
    
  • index.d.ts+18 2 modified
    @@ -1191,9 +1191,15 @@ declare module '@xmldom/xmldom' {
     		createAttributeNS(namespace: string | null, qualifiedName: string): Attr;
     
     		/**
    -		 * Returns a CDATASection node whose data is data.
    +		 * Returns a new CDATASection node whose data is `data`.
     		 *
    -		 * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/createCDATASection)
    +		 * __This implementation differs from the specification:__ - calling this method on an HTML
    +		 * document does not throw `NotSupportedError`.
    +		 *
    +		 * @throws {DOMException}
    +		 * With code `INVALID_CHARACTER_ERR` if `data` contains `"]]>"`.
    +		 * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createCDATASection
    +		 * @see https://dom.spec.whatwg.org/#dom-document-createcdatasection
     		 */
     		createCDATASection(data: string): CDATASection;
     
    @@ -1458,6 +1464,16 @@ declare module '@xmldom/xmldom' {
     	}
     
     	class XMLSerializer {
    +		/**
    +		 * Returns the result of serializing `node` to XML.
    +		 *
    +		 * __This implementation differs from the specification:__ - CDATASection nodes whose data
    +		 * contains `]]>` are serialized by splitting the section at each `]]>` occurrence (following
    +		 * W3C DOM Level 3 Core `split-cdata-sections`
    +		 * default behaviour). A configurable option is not yet implemented.
    +		 *
    +		 * @see https://html.spec.whatwg.org/#dom-xmlserializer-serializetostring
    +		 */
     		serializeToString(node: Node, nodeFilter?: (node: Node) => boolean): string;
     	}
     	// END ./lib/dom.js
    
  • lib/dom.js+26 1 modified
    @@ -2210,10 +2210,22 @@ Document.prototype = {
     		return node;
     	},
     	/**
    +	 * Returns a new CDATASection node whose data is `data`.
    +	 *
    +	 * __This implementation differs from the specification:__ - calling this method on an HTML
    +	 * document does not throw `NotSupportedError`.
    +	 *
     	 * @param {string} data
     	 * @returns {CDATASection}
    +	 * @throws {DOMException}
    +	 * With code `INVALID_CHARACTER_ERR` if `data` contains `"]]>"`.
    +	 * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createCDATASection
    +	 * @see https://dom.spec.whatwg.org/#dom-document-createcdatasection
     	 */
     	createCDATASection: function (data) {
    +		if (data.indexOf(']]>') !== -1) {
    +			throw new DOMException(DOMException.INVALID_CHARACTER_ERR, 'data contains "]]>"');
    +		}
     		var node = new CDATASection(PDC);
     		node.ownerDocument = this;
     		node.childNodes = new NodeList();
    @@ -2693,6 +2705,19 @@ function ProcessingInstruction(symbol) {
     ProcessingInstruction.prototype.nodeType = PROCESSING_INSTRUCTION_NODE;
     _extends(ProcessingInstruction, CharacterData);
     function XMLSerializer() {}
    +/**
    + * Returns the result of serializing `node` to XML.
    + *
    + * __This implementation differs from the specification:__ - CDATASection nodes whose data
    + * contains `]]>` are serialized by splitting the section at each `]]>` occurrence (following
    + * W3C DOM Level 3 Core `split-cdata-sections`
    + * default behaviour). A configurable option is not yet implemented.
    + *
    + * @param {Node} node
    + * @param {function} [nodeFilter]
    + * @returns {string}
    + * @see https://html.spec.whatwg.org/#dom-xmlserializer-serializetostring
    + */
     XMLSerializer.prototype.serializeToString = function (node, nodeFilter) {
     	return nodeSerializeToString.call(node, nodeFilter);
     };
    @@ -2917,7 +2942,7 @@ function serializeToString(node, buf, nodeFilter, visibleNamespaces) {
     			 */
     			return buf.push(node.data.replace(/[<&>]/g, _xmlEncoder));
     		case CDATA_SECTION_NODE:
    -			return buf.push(g.CDATA_START, node.data, g.CDATA_END);
    +			return buf.push(g.CDATA_START, node.data.replace(/]]>/g, ']]]]><![CDATA[>'), g.CDATA_END);
     		case COMMENT_NODE:
     			return buf.push(g.COMMENT_START, node.data, g.COMMENT_END);
     		case DOCUMENT_TYPE_NODE:
    
  • test/dom/cdata-section.test.js+43 2 modified
    @@ -1,7 +1,10 @@
     'use strict';
     
    -const { describe, test } = require('@jest/globals');
    -const { CDATASection } = require('../../lib/dom');
    +const { describe, expect, test } = require('@jest/globals');
    +const { CDATASection, DOMImplementation } = require('../../lib/dom');
    +const { DOMParser } = require('../../lib');
    +const { MIME_TYPE } = require('../../lib/conventions');
    +const { expectDOMException } = require('../errors/expectDOMException');
     
     describe('CDATASection.prototype', () => {
     	describe('constructor', () => {
    @@ -10,3 +13,41 @@ describe('CDATASection.prototype', () => {
     		});
     	});
     });
    +
    +describe('Document.prototype.createCDATASection', () => {
    +	let doc;
    +	beforeEach(() => {
    +		const impl = new DOMImplementation();
    +		doc = impl.createDocument(null, 'xml');
    +	});
    +
    +	describe('throws InvalidCharacterError', () => {
    +		test('when data is exactly "]]>"', () => {
    +			expectDOMException(() => doc.createCDATASection(']]>'), 'InvalidCharacterError');
    +		});
    +
    +		test('when data starts with safe content then contains "]]>"', () => {
    +			expectDOMException(() => doc.createCDATASection('safe]]>data'), 'InvalidCharacterError');
    +		});
    +
    +		test('when data contains multiple "]]>" occurrences', () => {
    +			expectDOMException(() => doc.createCDATASection('before]]>after]]>after'), 'InvalidCharacterError');
    +		});
    +	});
    +
    +	describe('does not throw', () => {
    +		test('when data is safe', () => {
    +			expect(() => doc.createCDATASection('safe')).not.toThrow();
    +		});
    +
    +		test('when data is empty string', () => {
    +			expect(() => doc.createCDATASection('')).not.toThrow();
    +		});
    +	});
    +
    +	describe('parse path', () => {
    +		test('parsing XML containing a CDATA section does not throw', () => {
    +			expect(() => new DOMParser().parseFromString('<root><![CDATA[some data]]></root>', MIME_TYPE.XML_TEXT)).not.toThrow();
    +		});
    +	});
    +});
    
  • test/dom/character-data.test.js+46 2 modified
    @@ -1,7 +1,9 @@
     'use strict';
     
    -const { describe, test } = require('@jest/globals');
    -const { CharacterData } = require('../../lib/dom');
    +const { describe, expect, test } = require('@jest/globals');
    +const { CharacterData, DOMImplementation } = require('../../lib/dom');
    +const { DOMParser, XMLSerializer } = require('../../lib');
    +const { MIME_TYPE } = require('../../lib/conventions');
     
     describe('CharacterData.prototype', () => {
     	describe('constructor', () => {
    @@ -10,3 +12,45 @@ describe('CharacterData.prototype', () => {
     		});
     	});
     });
    +
    +describe('CDATASection mutation vectors produce safe serializer output', () => {
    +	let doc;
    +	// Serializes, then re-parses, and returns true if an <injected> element appears in the tree.
    +	function isInjected(root) {
    +		const xml = new XMLSerializer().serializeToString(root);
    +		const reparsed = new DOMParser().parseFromString(xml, MIME_TYPE.XML_TEXT);
    +		return reparsed.getElementsByTagName('injected').length > 0;
    +	}
    +
    +	beforeEach(() => {
    +		doc = new DOMImplementation().createDocument(null, 'root', null);
    +	});
    +
    +	test('appendData introduces "]]>" safely', () => {
    +		const cdata = doc.createCDATASection('safe');
    +		doc.documentElement.appendChild(cdata);
    +		cdata.appendData(']]><injected/>');
    +		expect(isInjected(doc.documentElement)).toBe(false);
    +	});
    +
    +	test('replaceData introduces "]]>" safely', () => {
    +		const cdata = doc.createCDATASection('safe data');
    +		doc.documentElement.appendChild(cdata);
    +		cdata.replaceData(4, 5, ']]><injected/>');
    +		expect(isInjected(doc.documentElement)).toBe(false);
    +	});
    +
    +	test('.data assignment introduces "]]>" safely', () => {
    +		const cdata = doc.createCDATASection('safe');
    +		doc.documentElement.appendChild(cdata);
    +		cdata.data = 'evil]]><injected/>';
    +		expect(isInjected(doc.documentElement)).toBe(false);
    +	});
    +
    +	test('.textContent assignment introduces "]]>" safely', () => {
    +		const cdata = doc.createCDATASection('safe');
    +		doc.documentElement.appendChild(cdata);
    +		cdata.textContent = 'evil]]><injected/>';
    +		expect(isInjected(doc.documentElement)).toBe(false);
    +	});
    +});
    
  • test/dom/serializer.test.js+38 0 modified
    @@ -2,6 +2,7 @@
     
     const { DOMParser, XMLSerializer } = require('../../lib');
     const { MIME_TYPE } = require('../../lib/conventions');
    +const { DOMImplementation } = require('../../lib/dom');
     
     describe('XML Serializer', () => {
     	test('supports text node containing "]]>"', () => {
    @@ -227,3 +228,40 @@ describe('XML Serializer', () => {
     		});
     	});
     });
    +
    +describe('XMLSerializer CDATASection serialization', () => {
    +	let doc;
    +	beforeEach(() => {
    +		doc = new DOMImplementation().createDocument(null, 'root', null);
    +	});
    +
    +	test('serializes a safe CDATASection unchanged', () => {
    +		doc.documentElement.appendChild(doc.createCDATASection('safe data'));
    +		expect(new XMLSerializer().serializeToString(doc.documentElement)).toBe('<root><![CDATA[safe data]]></root>');
    +	});
    +
    +	test('splits a CDATASection whose data contains "]]>"', () => {
    +		const cdata = doc.createCDATASection('safe');
    +		cdata.data = 'foo]]>bar';
    +		doc.documentElement.appendChild(cdata);
    +		expect(new XMLSerializer().serializeToString(doc.documentElement)).toBe('<root><![CDATA[foo]]]]><![CDATA[>bar]]></root>');
    +	});
    +
    +	test('splits multiple "]]>" occurrences', () => {
    +		const cdata = doc.createCDATASection('safe');
    +		cdata.data = 'a]]>b]]>c';
    +		doc.documentElement.appendChild(cdata);
    +		expect(new XMLSerializer().serializeToString(doc.documentElement)).toBe(
    +			'<root><![CDATA[a]]]]><![CDATA[>b]]]]><![CDATA[>c]]></root>'
    +		);
    +	});
    +
    +	test('split output round-trips through DOMParser to equivalent content', () => {
    +		const cdata = doc.createCDATASection('safe');
    +		cdata.data = 'foo]]>bar';
    +		doc.documentElement.appendChild(cdata);
    +		const serialized = new XMLSerializer().serializeToString(doc.documentElement);
    +		const reparsed = new DOMParser().parseFromString(serialized, MIME_TYPE.XML_TEXT);
    +		expect(reparsed.documentElement.textContent).toBe('foo]]>bar');
    +	});
    +});
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.