CVE-2026-41672
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 comment content to be serialized into XML without validating or neutralizing comment-breaking sequences. As a result, an attacker can terminate the comment 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.
| Package | Affected versions | Patched versions |
|---|---|---|
@xmldom/xmldomnpm | < 0.8.13 | 0.8.13 |
@xmldom/xmldomnpm | >= 0.9.0, < 0.9.10 | 0.9.10 |
xmldomnpm | <= 0.6.0 | — |
Affected products
1Patches
2b39754088908fix: prevent XML injection via unsafe comment serialization (GHSA-j759-j44w-7fr8)
3 files changed · +59 −3
index.d.ts+8 −3 modified@@ -25,8 +25,11 @@ declare module "@xmldom/xmldom" { /** Options accepted by `XMLSerializer.prototype.serializeToString`. */ interface XMLSerializerOptions { /** - * When `true`, the serializer throws a DOMException with code `INVALID_STATE_ERR` if the - * CDATASection data contains `"]]>"`. + * When `true`, the serializer throws a DOMException with code `INVALID_STATE_ERR` if: + * - A CDATASection node's data contains `"]]>"`. + * - A Comment node's data contains `"-->"` (the injection sequence that terminates a + * comment). Comments whose data contains `"--"` but not `"-->"` are accepted on this + * branch — the 0.8.x parser does not validate bare `"--"` in comment content. * * @default false */ @@ -50,7 +53,9 @@ declare module "@xmldom/xmldom" { * * @throws {DOMException} * With code `INVALID_STATE_ERR` when `requireWellFormed` is `true` and the CDATASection - * data contains `"]]>"`. + * data contains `"]]>"`, or a Comment node's data contains `"-->"`. + * (On this 0.8.x branch, bare `"--"` in comment data does not throw — see + * `XMLSerializerOptions.requireWellFormed` for details.) * @see https://html.spec.whatwg.org/#dom-xmlserializer-serializetostring * @see https://github.com/w3c/DOM-Parsing/issues/84 */
lib/dom.js+11 −0 modified@@ -1506,6 +1506,11 @@ function XMLSerializer(){} * @throws {DOMException} * With code `INVALID_STATE_ERR` when `requireWellFormed` is `true` and the CDATASection * data contains `"]]>"`. + * With code `INVALID_STATE_ERR` when `requireWellFormed` is `true` and a Comment node's + * data contains `"-->"` (the injection sequence that terminates a comment and allows + * arbitrary XML to follow). Comments whose data contains `"--"` but not `"-->"` are + * accepted — the 0.8.x parser does not validate bare `"--"` inside comment content, + * so throwing would break a previously-working round-trip. * @see https://html.spec.whatwg.org/#dom-xmlserializer-serializetostring * @see https://github.com/w3c/DOM-Parsing/issues/84 */ @@ -1733,6 +1738,12 @@ function serializeToString(node,buf,isHTML,nodeFilter,visibleNamespaces,requireW } return buf.push('<![CDATA[', node.data.replace(/]]>/g, ']]]]><![CDATA[>'), ']]>'); case COMMENT_NODE: + if (requireWellFormed && node.data.indexOf('-->') !== -1) { + throw new DOMException( + INVALID_STATE_ERR, + 'The comment node data contains "-->"' + ); + } return buf.push( "<!--",node.data,"-->"); case DOCUMENT_TYPE_NODE: var pubid = node.publicId;
test/dom/serializer.test.js+40 −0 modified@@ -387,4 +387,44 @@ describe('XMLSerializer serializeToString requireWellFormed option', () => { doc.toString(false, null, { requireWellFormed: true }) ).toThrow(DOMException) }) + + describe('Comment', () => { + it('default: comment with "-->" in data emits verbatim — no throw', () => { + const comment = doc.createComment('safe-->evil') + doc.documentElement.appendChild(comment) + expect(new XMLSerializer().serializeToString(doc.documentElement)).toBe( + '<root><!--safe-->evil--></root>' + ) + }) + + it('requireWellFormed: true on comment with "-->" throws InvalidStateError', () => { + const comment = doc.createComment('safe-->evil') + doc.documentElement.appendChild(comment) + expect(() => + new XMLSerializer().serializeToString(doc, false, null, { + requireWellFormed: true, + }) + ).toThrow(DOMException) + }) + + it('requireWellFormed: true on comment with "--" (no "-->") does not throw — intentional on 0.8.x', () => { + const comment = doc.createComment('hello--world') + doc.documentElement.appendChild(comment) + expect( + new XMLSerializer().serializeToString(doc, false, null, { + requireWellFormed: true, + }) + ).toBe('<root><!--hello--world--></root>') + }) + + it('requireWellFormed: true on comment with clean data serializes correctly', () => { + const comment = doc.createComment('safe comment') + doc.documentElement.appendChild(comment) + expect( + new XMLSerializer().serializeToString(doc, false, null, { + requireWellFormed: true, + }) + ).toBe('<root><!--safe comment--></root>') + }) + }) })
fda7cc313de3fix: prevent XML injection via unsafe comment serialization (GHSA-j759-j44w-7fr8)
3 files changed · +76 −5
index.d.ts+8 −2 modified@@ -1209,6 +1209,11 @@ declare module '@xmldom/xmldom' { /** * Creates a comment object with the specified data. * + * No validation is performed at creation time. When the resulting document is serialized + * with `requireWellFormed: true`, the serializer throws `InvalidStateError` if the comment + * data contains `--` anywhere, ends with `-`, or contains characters outside the XML Char + * production (W3C DOM Parsing §3.2.1.3). Without that option the data is emitted verbatim. + * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/createComment) */ createComment(data: string): Comment; @@ -1470,8 +1475,9 @@ declare module '@xmldom/xmldom' { interface XMLSerializerOptions { /** * When `true`, the serializer throws `InvalidStateError` for content that would produce - * ill-formed XML (CDATASection data containing `"]]>"`, Text data with characters outside - * the XML Char production, or a Document with no `documentElement`). + * ill-formed XML: CDATASection data containing `"]]>"`; Text data with characters outside + * the XML Char production; a Comment node whose data contains `--` anywhere or ends with + * `-`; or a Document with no `documentElement`. * * @default false */
lib/dom.js+24 −3 modified@@ -2218,6 +2218,15 @@ Document.prototype = { /** * @param {string} data * @returns {Comment} + * @see https://dom.spec.whatwg.org/#dom-document-createcomment + * @see https://www.w3.org/TR/xml/#NT-Comment XML 1.0 production [15] + * @see https://www.w3.org/TR/DOM-Parsing/#dfn-concept-serialize-xml §3.2.1.3 + * + * Note: no validation is performed at creation time. When the resulting document is + * serialized with `requireWellFormed: true`, the serializer throws `InvalidStateError` + * if the comment data contains `--` anywhere, ends with `-`, or contains characters + * outside the XML Char production (W3C DOM Parsing §3.2.1.3). Without that option the + * data is emitted verbatim. */ createComment: function (data) { var node = new Comment(PDC); @@ -2761,9 +2770,10 @@ function XMLSerializer() {} * A filter function applied to each node before serialization. * @returns {string} * @throws {DOMException} - * With name `InvalidStateError` when `requireWellFormed` is `true` and the CDATASection data - * contains `"]]>"`, Text data contains characters outside the XML Char production, or the - * Document has no `documentElement`. + * 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`. * @see https://html.spec.whatwg.org/#dom-xmlserializer-serializetostring * @see https://github.com/w3c/DOM-Parsing/issues/84 */ @@ -3022,6 +3032,17 @@ function serializeToString(node, buf, visibleNamespaces, opts) { } return buf.push(g.CDATA_START, node.data, g.CDATA_END); case COMMENT_NODE: + if (requireWellFormed) { + if (g.InvalidChar.test(node.data)) { + throw new DOMException( + 'The comment node data contains characters outside the XML Char production', + 'InvalidStateError' + ); + } + if (node.data.indexOf('--') !== -1 || node.data[node.data.length - 1] === '-') { + throw new DOMException('The comment node data contains "--" or ends with "-"', 'InvalidStateError'); + } + } return buf.push(g.COMMENT_START, node.data, g.COMMENT_END); case DOCUMENT_TYPE_NODE: var pubid = node.publicId;
test/dom/serializer.test.js+44 −0 modified@@ -309,6 +309,50 @@ describe('XMLSerializer.serializeToString', () => { expect(() => new XMLSerializer().serializeToString(doc, { requireWellFormed: true })).not.toThrow(); }); }); + + describe('Comment', () => { + test('default: comment with "-->" in data emits verbatim — no throw', () => { + doc.documentElement.appendChild(doc.createComment('hello-->world')); + expect(new XMLSerializer().serializeToString(doc)).toBe('<root><!--hello-->world--></root>'); + }); + + test('default: comment with "--" in data emits verbatim — no throw', () => { + doc.documentElement.appendChild(doc.createComment('hello--world')); + expect(new XMLSerializer().serializeToString(doc)).toBe('<root><!--hello--world--></root>'); + }); + + test('requireWellFormed: true on comment with invalid XML Char (\\x00) throws InvalidStateError', () => { + doc.documentElement.appendChild(doc.createComment('hello\x00world')); + expectDOMException(() => new XMLSerializer().serializeToString(doc, { requireWellFormed: true }), 'InvalidStateError'); + }); + + test('requireWellFormed: true on comment with "-->" throws InvalidStateError', () => { + doc.documentElement.appendChild(doc.createComment('hello-->world')); + expectDOMException(() => new XMLSerializer().serializeToString(doc, { requireWellFormed: true }), 'InvalidStateError'); + }); + + test('requireWellFormed: true on comment with "--" (no "-->") throws InvalidStateError', () => { + doc.documentElement.appendChild(doc.createComment('hello--world')); + expectDOMException(() => new XMLSerializer().serializeToString(doc, { requireWellFormed: true }), 'InvalidStateError'); + }); + + test('requireWellFormed: true on comment whose data ends with "-" throws InvalidStateError', () => { + doc.documentElement.appendChild(doc.createComment('hello-')); + expectDOMException(() => new XMLSerializer().serializeToString(doc, { requireWellFormed: true }), 'InvalidStateError'); + }); + + test('requireWellFormed: true on comment with clean data does not throw', () => { + doc.documentElement.appendChild(doc.createComment('clean comment')); + expect(() => new XMLSerializer().serializeToString(doc, { requireWellFormed: true })).not.toThrow(); + }); + + test('mutation vector: appendData("-->") then requireWellFormed: true throws InvalidStateError', () => { + const comment = doc.createComment('clean'); + doc.documentElement.appendChild(comment); + comment.appendData('-->'); + 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
8- github.com/advisories/GHSA-j759-j44w-7fr8ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-41672ghsaADVISORY
- github.com/xmldom/xmldom/commit/b397540889086da868c30c366ad5c220d1a750c7nvdWEB
- github.com/xmldom/xmldom/commit/fda7cc313de30243fea35cada64e0bb12099c2a1nvdWEB
- github.com/xmldom/xmldom/pull/987nvdWEB
- github.com/xmldom/xmldom/releases/tag/0.8.13nvdWEB
- github.com/xmldom/xmldom/releases/tag/0.9.10nvdWEB
- github.com/xmldom/xmldom/security/advisories/GHSA-j759-j44w-7fr8nvdWEB
News mentions
1- Patch Tuesday - May 2026Rapid7 Blog · May 13, 2026