VYPR
High severityNVD Advisory· Published May 7, 2026· Updated May 7, 2026

CVE-2026-41675

CVE-2026-41675

Description

xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) DOMParser and XMLSerializer module. In @xmldom/xmldom prior to versions 0.9.10 and 0.8.13 and xmldom version 0.6.0 and prior, the package allows attacker-controlled processing instruction data to be serialized into XML without validating or neutralizing the PI-closing sequence ?>. As a result, an attacker can terminate the processing instruction early and inject arbitrary XML nodes into the serialized output. This issue has been patched in versions @xmldom/xmldom versions 0.9.10 and 0.8.13.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
@xmldom/xmldomnpm
< 0.8.130.8.13
@xmldom/xmldomnpm
>= 0.9.0, < 0.9.100.9.10
xmldomnpm
<= 0.6.0

Affected products

1

Patches

1
7207a4b0e0bc

fix: prevent XML injection via unsafe PI serialization (GHSA-x6wf-f3px-wcqx)

https://github.com/xmldom/xmldomkarfauApr 12, 2026via ghsa
4 files changed · +144 12
  • index.d.ts+23 8 modified
    @@ -1267,12 +1267,21 @@ declare module '@xmldom/xmldom' {
     		createEntityReference(name: string): EntityReference;
     
     		/**
    -		 * Returns a ProcessingInstruction node whose target is target and data is data. If target does
    -		 * not match the Name production an "InvalidCharacterError" DOMException will be thrown. If
    -		 * data contains "?>" an "InvalidCharacterError" DOMException will be thrown.
    +		 * Returns a ProcessingInstruction node whose target is target and data is data.
     		 *
    -		 * [MDN
    -		 * Reference](https://developer.mozilla.org/docs/Web/API/Document/createProcessingInstruction)
    +		 * __This behavior is slightly different from the in the specs__:
    +		 * - it does not do any input validation on the arguments and doesn't throw
    +		 * "InvalidCharacterError".
    +		 *
    +		 * Note: When the resulting document is serialized with `requireWellFormed: true`, the
    +		 * serializer throws `InvalidStateError` if `.target` contains `:` or is an ASCII
    +		 * case-insensitive match for `"xml"`, or if `.data` contains `?>` or characters outside the
    +		 * XML Char production (W3C DOM Parsing §3.2.1.7). Without that option the data is emitted
    +		 * verbatim.
    +		 *
    +		 * @see https://developer.mozilla.org/docs/Web/API/Document/createProcessingInstruction
    +		 * @see https://dom.spec.whatwg.org/#dom-document-createprocessinginstruction
    +		 * @see https://www.w3.org/TR/DOM-Parsing/#dfn-concept-serialize-xml §3.2.1.7
     		 */
     		createProcessingInstruction(
     			target: string,
    @@ -1516,9 +1525,15 @@ declare module '@xmldom/xmldom' {
     		 * breaking milestone.
     		 *
     		 * @throws {DOMException}
    -		 * `InvalidStateError` when `requireWellFormed` is `true` and CDATASection data contains
    -		 * `"]]>"`, Text data contains characters outside the XML Char production, or the Document
    -		 * has no `documentElement`.
    +		 * `InvalidStateError` when `requireWellFormed` is `true` and any of the following conditions
    +		 * hold:
    +		 * - CDATASection data contains `"]]>"`
    +		 * - Text data contains characters outside the XML Char production - a Comment node's data
    +		 * contains `--` anywhere or ends with `-`
    +		 * - a ProcessingInstruction's target contains `:` or is an ASCII case-insensitive match for
    +		 * `"xml"`, or its data contains `?>` or characters outside the XML Char production - the
    +		 * Document has no `documentElement`
    +		 * @see https://developer.mozilla.org/docs/Web/API/XMLSerializer/serializeToString
     		 * @see https://html.spec.whatwg.org/#dom-xmlserializer-serializetostring
     		 * @see https://github.com/w3c/DOM-Parsing/issues/84
     		 */
    
  • lib/dom.js+37 3 modified
    @@ -2259,9 +2259,24 @@ Document.prototype = {
     		return node;
     	},
     	/**
    +	 * Returns a ProcessingInstruction node whose target is target and data is data.
    +	 *
    +	 * __This behavior is slightly different from the in the specs__:
    +	 * - it does not do any input validation on the arguments and doesn't throw
    +	 * "InvalidCharacterError".
    +	 *
    +	 * Note: When the resulting document is serialized with `requireWellFormed: true`, the
    +	 * serializer throws `InvalidStateError` if `.target` contains `:` or is an ASCII
    +	 * case-insensitive match for `"xml"`, or if `.data` contains `?>` or characters outside the
    +	 * XML Char production (W3C DOM Parsing §3.2.1.7). Without that option the data is emitted
    +	 * verbatim.
    +	 *
     	 * @param {string} target
     	 * @param {string} data
     	 * @returns {ProcessingInstruction}
    +	 * @see https://developer.mozilla.org/docs/Web/API/Document/createProcessingInstruction
    +	 * @see https://dom.spec.whatwg.org/#dom-document-createprocessinginstruction
    +	 * @see https://www.w3.org/TR/DOM-Parsing/#dfn-concept-serialize-xml §3.2.1.7
     	 */
     	createProcessingInstruction: function (target, data) {
     		var node = new ProcessingInstruction(PDC);
    @@ -2771,9 +2786,14 @@ function XMLSerializer() {}
      * @returns {string}
      * @throws {DOMException}
      * With name `InvalidStateError` when `requireWellFormed` is `true` and any of the following
    - * conditions hold: CDATASection data contains `"]]>"`; Text data contains characters outside
    - * the XML Char production; a Comment node's data contains `--` anywhere or ends with `-`;
    - * the Document has no `documentElement`.
    + * conditions hold:
    + * - CDATASection data contains `"]]>"`
    + * - Text data contains characters outside the XML Char production - a Comment node's data
    + * contains `--` anywhere or ends with `-`
    + * - a ProcessingInstruction's target contains `:` or is an ASCII case-insensitive match for
    + * `"xml"`, or its data contains `?>` or characters outside the XML Char production the
    + * Document has no `documentElement`.
    + * @see https://developer.mozilla.org/docs/Web/API/XMLSerializer/serializeToString
      * @see https://html.spec.whatwg.org/#dom-xmlserializer-serializetostring
      * @see https://github.com/w3c/DOM-Parsing/issues/84
      */
    @@ -3062,6 +3082,20 @@ function serializeToString(node, buf, visibleNamespaces, opts) {
     			buf.push('>');
     			return;
     		case PROCESSING_INSTRUCTION_NODE:
    +			if (requireWellFormed) {
    +				if (node.target.indexOf(':') !== -1 || node.target.toLowerCase() === 'xml') {
    +					throw new DOMException('The ProcessingInstruction target is not well-formed', 'InvalidStateError');
    +				}
    +				if (g.InvalidChar.test(node.data)) {
    +					throw new DOMException(
    +						'The ProcessingInstruction data contains characters outside the XML Char production',
    +						'InvalidStateError'
    +					);
    +				}
    +				if (node.data.indexOf('?>') !== -1) {
    +					throw new DOMException('The ProcessingInstruction data contains "?>"', 'InvalidStateError');
    +				}
    +			}
     			return buf.push('<?', node.target, ' ', node.data, '?>');
     		case ENTITY_REFERENCE_NODE:
     			return buf.push('&', node.nodeName, ';');
    
  • test/dom/processing-instruction.test.js+9 1 modified
    @@ -2,7 +2,8 @@
     
     const { DOMParser, XMLSerializer } = require('../../lib');
     const { MIME_TYPE } = require('../../lib/conventions');
    -const { Node, ProcessingInstruction } = require('../../lib/dom');
    +const { DOMImplementation, Node, ProcessingInstruction } = require('../../lib/dom');
    +const { expectDOMException } = require('../errors/expectDOMException');
     
     describe('ProcessingInstruction', () => {
     	describe('constructor', () => {
    @@ -51,4 +52,11 @@ describe('ProcessingInstruction', () => {
     		pi.data = 'href="newcss.css" type="text/css"';
     		expect(pi.data).toBe('href="newcss.css" type="text/css"');
     	});
    +
    +	test('createProcessingInstruction accepts any target or data without throwing', () => {
    +		const doc = new DOMParser().parseFromString('<xml></xml>', MIME_TYPE.XML_TEXT);
    +		expect(() => doc.createProcessingInstruction('ns:bad', 'data')).not.toThrow();
    +		expect(() => doc.createProcessingInstruction('xml', '?>')).not.toThrow();
    +		expect(() => doc.createProcessingInstruction('foo', 'inject?>evil')).not.toThrow();
    +	});
     });
    
  • test/dom/serializer.test.js+75 0 modified
    @@ -353,6 +353,81 @@ describe('XMLSerializer.serializeToString', () => {
     				expectDOMException(() => new XMLSerializer().serializeToString(doc, { requireWellFormed: true }), 'InvalidStateError');
     			});
     		});
    +
    +		describe('ProcessingInstruction', () => {
    +			test('default: PI with ":" in target emits verbatim — no throw', () => {
    +				const pi = doc.createProcessingInstruction('ns:target', 'data');
    +				doc.documentElement.appendChild(pi);
    +				expect(() => new XMLSerializer().serializeToString(doc)).not.toThrow();
    +			});
    +
    +			test('default: PI with target "xml" emits verbatim — no throw', () => {
    +				const pi = doc.createProcessingInstruction('xml', 'version="1.0"');
    +				doc.documentElement.appendChild(pi);
    +				expect(() => new XMLSerializer().serializeToString(doc)).not.toThrow();
    +			});
    +
    +			test('default: PI with invalid XML Char in data emits verbatim — no throw', () => {
    +				const pi = doc.createProcessingInstruction('foo', 'data\x00here');
    +				doc.documentElement.appendChild(pi);
    +				expect(() => new XMLSerializer().serializeToString(doc)).not.toThrow();
    +			});
    +
    +			test('default: PI with "?>" in data emits verbatim — no throw', () => {
    +				const pi = doc.createProcessingInstruction('foo', 'inject?>evil');
    +				doc.documentElement.appendChild(pi);
    +				expect(new XMLSerializer().serializeToString(doc)).toBe('<root><?foo inject?>evil?></root>');
    +			});
    +
    +			test('requireWellFormed: true on PI with ":" in target throws InvalidStateError', () => {
    +				const pi = doc.createProcessingInstruction('ns:target', 'data');
    +				doc.documentElement.appendChild(pi);
    +				expectDOMException(() => new XMLSerializer().serializeToString(doc, { requireWellFormed: true }), 'InvalidStateError');
    +			});
    +
    +			test('requireWellFormed: true on PI with target "xml" throws InvalidStateError', () => {
    +				const pi = doc.createProcessingInstruction('xml', 'version="1.0"');
    +				doc.documentElement.appendChild(pi);
    +				expectDOMException(() => new XMLSerializer().serializeToString(doc, { requireWellFormed: true }), 'InvalidStateError');
    +			});
    +
    +			test('requireWellFormed: true on PI with target "XML" (uppercase) throws InvalidStateError', () => {
    +				const pi = doc.createProcessingInstruction('XML', 'data');
    +				doc.documentElement.appendChild(pi);
    +				expectDOMException(() => new XMLSerializer().serializeToString(doc, { requireWellFormed: true }), 'InvalidStateError');
    +			});
    +
    +			test('requireWellFormed: true on PI with target "Xml" (mixed case) throws InvalidStateError', () => {
    +				const pi = doc.createProcessingInstruction('Xml', 'data');
    +				doc.documentElement.appendChild(pi);
    +				expectDOMException(() => new XMLSerializer().serializeToString(doc, { requireWellFormed: true }), 'InvalidStateError');
    +			});
    +
    +			test('requireWellFormed: true on PI with invalid XML Char (\\x00) in data throws InvalidStateError', () => {
    +				const pi = doc.createProcessingInstruction('foo', 'data\x00here');
    +				doc.documentElement.appendChild(pi);
    +				expectDOMException(() => new XMLSerializer().serializeToString(doc, { requireWellFormed: true }), 'InvalidStateError');
    +			});
    +
    +			test('requireWellFormed: true on PI with "?>" in data throws InvalidStateError', () => {
    +				const pi = doc.createProcessingInstruction('foo', 'inject?>evil');
    +				doc.documentElement.appendChild(pi);
    +				expectDOMException(() => new XMLSerializer().serializeToString(doc, { requireWellFormed: true }), 'InvalidStateError');
    +			});
    +
    +			test('requireWellFormed: true on PI with clean target and data does not throw', () => {
    +				const pi = doc.createProcessingInstruction('xml-stylesheet', 'href="style.css"');
    +				doc.documentElement.appendChild(pi);
    +				expect(() => new XMLSerializer().serializeToString(doc, { requireWellFormed: true })).not.toThrow();
    +			});
    +
    +			test('mutation vector: set PI data to "?>" then requireWellFormed: true throws InvalidStateError', () => {
    +				const pi = doc.createProcessingInstruction('foo', 'clean');
    +				doc.documentElement.appendChild(pi);
    +				pi.data = 'inject?>evil';
    +				expectDOMException(() => new XMLSerializer().serializeToString(doc, { requireWellFormed: true }), 'InvalidStateError');
    +			});
    +		});
     	});
     });
     
    

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

1