Public mode

No access to your tenant or devices. Every item links to its source. What this means

Sources need attention Customize Public modeDiscuss ChangeIntel on Discord

xmldom: XML injection through unvalidated DocumentType serialization

CVSS 4.0 8.7 HIGHEUVD CVSS 4.0 8.7 CVSS 4.0 · [email protected] · Secondary 8.7 HIGHCVSS 3.1 · 0b0ca135-0b70-47e7-9f44-1890c2a1c46c · Secondary 7.5 HIGHCVSS_V4 Publisher rating HIGHFixed by @xmldom/xmldom 0.8.13Fixed by @xmldom/xmldom 0.9.10KEV status unknown

cve-program · CVE-2026-41674

Published

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 serializes DocumentType node fields (internalSubset, publicId, systemId) verbatim without any escaping or validation. When these fields are set programmatically to attacker-controlled strings, XMLSerializer.serializeToString can produce output where the DOCTYPE declaration is terminated early and arbitrary markup appears outside it. This issue has been patched in versions @xmldom/xmldom versions 0.9.10 and 0.8.13.

Read the publisher's advisory

euvd · EUVD-2026-28289

Published

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 serializes DocumentType node fields (internalSubset, publicId, systemId) verbatim without any escaping or validation. When these fields are set programmatically to attacker-controlled strings, XMLSerializer.serializeToString can produce output where the DOCTYPE declaration is terminated early and arbitrary markup appears outside it. This issue has been patched in versions @xmldom/xmldom versions 0.9.10 and 0.8.13.

Read the publisher's advisory

nvd · CVE-2026-41674

Published

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 serializes DocumentType node fields (internalSubset, publicId, systemId) verbatim without any escaping or validation. When these fields are set programmatically to attacker-controlled strings, XMLSerializer.serializeToString can produce output where the DOCTYPE declaration is terminated early and arbitrary markup appears outside it. This issue has been patched in versions @xmldom/xmldom versions 0.9.10 and 0.8.13.

Read the publisher's advisory

osv · GHSA-f6ww-3ggp-fr8h

Published

## Summary The package serializes `DocumentType` node fields (`internalSubset`, `publicId`, `systemId`) verbatim without any escaping or validation. When these fields are set programmatically to attacker-controlled strings, `XMLSerializer.serializeToString` can produce output where the DOCTYPE declaration is terminated early and arbitrary markup appears outside it. --- ## Details `DOMImplementation.createDocumentType(qualifiedName, publicId, systemId, internalSubset)` validates only `qualifiedName` against the XML QName production. The remaining three arguments are stored as-is with no validation. The XMLSerializer emits `DocumentType` nodes as: ``` <!DOCTYPE name[ PUBLIC pubid][ SYSTEM sysid][ [internalSubset]]> ``` All fields are pushed into the output buffer verbatim — no escaping, no quoting added. **`internalSubset` injection:** The serializer wraps `internalSubset` with ` [` and `]`. A value containing `]>` closes the internal subset and the DOCTYPE declaration at the injection point. Any content after `]>` in `internalSubset` appears outside the DOCTYPE in the serialized output as raw XML markup. Reported by @TharVid (GHSA-f6ww-3ggp-fr8h). Affected: `@xmldom/xmldom` ≥ 0.9.0 via `createDocumentType` API; 0.8.x only via direct property write. **`publicId` injection:** The serializer emits `publicId` verbatim after `PUBLIC` with no quoting added. A value containing an injected system identifier (e.g., `"pubid" SYSTEM "evil"`) breaks the intended quoting context, injecting a fake SYSTEM entry into the serialized DOCTYPE declaration. Identified during internal security research. Affected: both branches, all versions back to 0.1.0. **`systemId` injection:** The serializer emits `systemId` verbatim. A value containing `>` terminates the DOCTYPE declaration early; content after `>` appears as raw XML markup outside the DOCTYPE context. Identified during internal security research. Affected: both branches, all versions back to 0.1.0. The parse path is safe: the SAX parser enforces the `PubidLiteral` and `SystemLiteral` grammar productions, which exclude the relevant characters, and the internal subset parser only accepts a subset it can structurally validate. The vulnerability is reachable only through programmatic `createDocumentType` calls with attacker-controlled arguments. --- ## Affected code **`lib/dom.js` — `createDocumentType` (lines 898–910):** ```js createDocumentType: function (qualifiedName, publicId, systemId, internalSubset) { validateQualifiedName(qualifiedName); // only qualifiedName is validated var node = new DocumentType(PDC); node.name = qualifiedName; node.nodeName = qualifiedName; node.publicId = publicId || ''; // stored verbatim node.systemId = systemId || ''; // stored verbatim node.internalSubset = internalSubset || ''; // stored verbatim node.childNodes = new NodeList(); return node; }, ``` **`lib/dom.js` — serializer DOCTYPE case (lines 2948–2964):** ```js case DOCUMENT_TYPE_NODE: var pubid = node.publicId; var sysid = node.systemId; buf.push(g.DOCTYPE_DECL_START, ' ', node.name); if (pubid) { buf.push(' ', g.PUBLIC, ' ', pubid); if (sysid && sysid !== '.') { buf.push(' ', sysid); } } else if (sysid && sysid !== '.') { buf.push(' ', g.SYSTEM, ' ', sysid); } if (node.internalSubset) { buf.push(' [', node.internalSubset, ']'); // internalSubset emitted verbatim } buf.push('>'); return; ``` --- ## PoC ### internalSubset injection ```js const { DOMImplementation, XMLSerializer } = require('@xmldom/xmldom'); const impl = new DOMImplementation(); const doctype = impl.createDocumentType( 'root', '', '', ']><injected/><![CDATA[' ); const doc = impl.createDocument(null, 'root', doctype); const xml = new XMLSerializer().serializeToString(doc); console.log(xml); // <!DOCTYPE root []><injected/><![CDATA[]><root/> // ^^^^^^^^^^ injected element outside DOCTYPE ``` ### publicId quoting context break ```js const { DOMImplementation, XMLSerializer } = require('@xmldom/xmldom'); const impl = new DOMImplementation(); const doctype = impl.createDocumentType( 'root', '"injected PUBLIC_ID" SYSTEM "evil"', '', '' ); const doc = impl.createDocument(null, 'root', doctype); console.log(new XMLSerializer().serializeToString(doc)); // <!DOCTYPE root PUBLIC "injected PUBLIC_ID" SYSTEM "evil"><root/> // quoting context broken — SYSTEM entry injected ``` ### systemId injection ```js const { DOMImplementation, XMLSerializer } = require('@xmldom/xmldom'); const impl = new DOMImplementation(); const doctype = impl.createDocumentType( 'root', '', '"sysid"><injected attr="pwn"/>', '' ); const doc = impl.createDocument(null, 'root', doctype); console.log(new XMLSerializer().serializeToString(doc)); // <!DOCTYPE root SYSTEM "sysid"><injected attr="pwn"/>><root/> // > in sysid closes DOCTYPE early; <injected/> appears as sibling element ``` --- ## Impact An application that programmatically constructs `DocumentType` nodes from user-controlled data and then serializes the document can emit a DOCTYPE declaration where the internal subset is closed early or where injected SYSTEM entities or other declarations appear in the serialized output. Downstream XML parsers that re-parse the serialized output and expand entities from the injected DOCTYPE declarations may be susceptible to XXE-class attacks if they enable entity expansion. --- ## Fix Applied > **⚠ Opt-in required.** Protection is not automatic. Existing serialization calls remain > vulnerable unless `{ requireWellFormed: true }` is explicitly passed. Applications that pass > untrusted data to `createDocumentType()` or write untrusted values directly to a > `DocumentType` node's `publicId`, `systemId`, or `internalSubset` properties should audit > all `serializeToString()` call sites and add the option. `XMLSerializer.serializeToString()` now accepts an options object as a second argument. When `{ requireWellFormed: true }` is passed, the serializer validates the `DocumentType` node's `publicId`, `systemId`, and `internalSubset` fields before emitting the DOCTYPE declaration and throws `InvalidStateError` if any field contains an injection sequence: - **`publicId`**: throws if non-empty and does not match the XML `PubidLiteral` production (XML 1.0 [12]) - **`systemId`**: throws if non-empty and does not match the XML `SystemLiteral` production (XML 1.0 [11]) - **`internalSubset`**: throws if it contains `]>` (which closes the internal subset and DOCTYPE declaration early) All three checks apply regardless of how the invalid value entered the node — whether via `createDocumentType` arguments or a subsequent direct property write. ### PoC — fixed path ```js const { DOMImplementation, XMLSerializer } = require('@xmldom/xmldom'); const impl = new DOMImplementation(); // internalSubset injection const dt1 = impl.createDocumentType('root', '', '', ']><injected/><![CDATA['); const doc1 = impl.createDocument(null, 'root', dt1); // Default (unchanged): verbatim — injection present console.log(new XMLSerializer().serializeToString(doc1)); // <!DOCTYPE root []><injected/><![CDATA[]><root/> // Opt-in guard: throws InvalidStateError try { new XMLSerializer().serializeToString(doc1, { requireWellFormed: true }); } catch (e) { console.log(e.name, e.message); // InvalidStateError: DocumentType internalSubset contains "]>" } ``` The guard also covers post-creation property writes: ```js const dt2 = impl.createDocumentType('root', '', ''); dt2.systemId = '"sysid"><injected attr="pwn"/>'; const doc2 = impl.createDocument(null, 'root', dt2); new XMLSerializer().serializeToString(doc2, { requireWellFormed: true }); // InvalidStateError: DocumentType systemId is not a valid SystemLiteral ``` ### Why the default stays verbatim The W3C DOM Parsing and Serialization spec §3.2.1.3 defines a `require well-formed` flag whose **default value is `false`**. With the flag unset, the spec permits verbatim serialization of DOCTYPE fields. Unconditionally throwing would be a behavioral breaking change with no spec justification. The opt-in `requireWellFormed: true` flag allows applications that require injection safety to enable strict mode without breaking existing deployments. ### Residual limitation `createDocumentType(qualifiedName, publicId, systemId[, internalSubset])` does not validate `publicId`, `systemId`, or `internalSubset` at creation time. This creation-time validation is a breaking change and is deferred to a future breaking release. When the default serialization path is used (without `requireWellFormed: true`), all three fields are still emitted verbatim. Applications that do not pass `requireWellFormed: true` remain exposed.

Read the publisher's advisory

Affected versions, risk and source history

Exploitation and risk

Source status

CISA KEV lists vulnerabilities known to be exploited. FIRST EPSS estimates the likelihood of exploitation in the next 30 days. An unavailable score does not mean zero risk.

Which CVEs have scores here?

This view includes predictions for CVE identifiers explicitly listed in public NuGet advisories. Microsoft release pages have their own prediction coverage. Check source status for the scope and latest checks.

No exploitation or prediction data is available here for these CVEs. This does not mean they are safe or unlikely to be exploited.

cve-program · CVE-2026-41674

Published

xmldom: XML injection through unvalidated DocumentType serialization

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 serializes DocumentType node fields (internalSubset, publicId, systemId) verbatim without any escaping or validation. When these fields are set programmatically to attacker-controlled strings, XMLSerializer.serializeToString can produce output where the DOCTYPE declaration is terminated early and arbitrary markup appears outside it. This issue has been patched in versions @xmldom/xmldom versions 0.9.10 and 0.8.13.

Source published
7 May 2026 03:47:51 UTC
Source modified
10 Sep 2026 12:05:00 UTC
Saved here
23 Oct 2026 15:48:27 UTC
Source terms
CVE Program Terms of Use

Original source record · Cite this exact observation · Complete original publisher JSON

Source: cve-program · CVE Program Terms of Use. The publisher's wording is preserved. ChangeIntel groups the information and labels changes. The JSON record includes additional source fields.

CVSS 4.0 · 8.7 CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N

euvd · EUVD-2026-28289

Published

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 serializes DocumentType node…

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 serializes DocumentType node fields (internalSubset, publicId, systemId) verbatim without any escaping or validation. When these fields are set programmatically to attacker-controlled strings, XMLSerializer.serializeToString can produce output where the DOCTYPE declaration is terminated early and arbitrary markup appears outside it. This issue has been patched in versions @xmldom/xmldom versions 0.9.10 and 0.8.13.

Additional publisher context

EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON.

Original publisher date fields

datePublished: May 7, 2026, 3:47:51 AM

dateUpdated: Sep 10, 2026, 12:05:00 PM

5 related source bulletins · separate from aliases

RHSA-2026:26234: Red Hat Security Advisory: Red Hat Developer Hub 1.9.5 release.

CVE-2026-6321 · CVE-2026-6322 · CVE-2026-24781 · CVE-2026-32281 · CVE-2026-41242 · CVE-2026-41672 · CVE-2026-41673 · CVE-2026-41674 · CVE-2026-41675 · CVE-2026-44293

RHSA-2026:20034: Red Hat Security Advisory: OpenShift Container Platform 4.21.17 bug fix and security update

CVE-2025-58183 · CVE-2026-29063 · CVE-2026-33186 · CVE-2026-34986 · CVE-2026-35469 · CVE-2026-41674

RHSA-2026:21338: Red Hat Security Advisory: Red Hat Developer Hub 1.8.7 release.

CVE-2026-6321 · CVE-2026-32280 · CVE-2026-32281 · CVE-2026-32282 · CVE-2026-40895 · CVE-2026-41240 · CVE-2026-41242 · CVE-2026-41674 · CVE-2026-42033 · CVE-2026-42035 · CVE-2026-42039 · CVE-2026-42041 · CVE-2026-42043 · CVE-2026-42044

msrc_CVE-2026-41674: xmldom: XML injection through unvalidated DocumentType serialization

CVE-2026-41674

RHSA-2026:21703: Red Hat Security Advisory: OpenShift Container Platform 4.20.24 bug fix and security update

CVE-2026-26996 · CVE-2026-29063 · CVE-2026-33186 · CVE-2026-34986 · CVE-2026-41674

Source published
May 7, 2026, 3:47:51 AM (timezone not stated)
Source modified
Sep 10, 2026, 12:05:00 PM (timezone not stated)
Saved here
13 Nov 2026 13:08:39 UTC
Source terms
ENISA EUVD · source terms and attribution apply

Original source record · Cite this exact observation · Complete original publisher JSON

Source: euvd · ENISA EUVD · source terms and attribution apply. The publisher's wording is preserved. ChangeIntel groups the information and labels changes. The JSON record includes additional source fields.

EUVD CVSS 4.0 · 8.7 CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N

nvd · CVE-2026-41674

Published

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 serializes DocumentType node…

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 serializes DocumentType node fields (internalSubset, publicId, systemId) verbatim without any escaping or validation. When these fields are set programmatically to attacker-controlled strings, XMLSerializer.serializeToString can produce output where the DOCTYPE declaration is terminated early and arbitrary markup appears outside it. This issue has been patched in versions @xmldom/xmldom versions 0.9.10 and 0.8.13.

Additional publisher context

NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON.

Source analysis status: Deferred

Weakness classifications: CWE-91

Original publisher date fields

published: 2026-05-07T04:16:33.433

lastModified: 2026-09-10T13:20:02.843

Source published
7 May 2026 04:16:33 UTC
Source modified
10 Sep 2026 13:20:02 UTC
Saved here
25 Oct 2026 09:17:41 UTC
Source terms
NIST NVD usage terms; underlying source attribution retained

Original source record · Cite this exact observation · Complete original publisher JSON

Source: nvd · NIST NVD usage terms; underlying source attribution retained. The publisher's wording is preserved. ChangeIntel groups the information and labels changes. The JSON record includes additional source fields.

CVSS 4.0 · [email protected] · Secondary · 8.7 CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

CVSS 3.1 · 0b0ca135-0b70-47e7-9f44-1890c2a1c46c · Secondary · 7.5 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N

osv · GHSA-f6ww-3ggp-fr8h

Published

xmldom has XML injection through unvalidated DocumentType serialization

## Summary The package serializes `DocumentType` node fields (`internalSubset`, `publicId`, `systemId`) verbatim without any escaping or validation. When these fields are set programmatically to attacker-controlled strings, `XMLSerializer.serializeToString` can produce output where the DOCTYPE declaration is terminated early and arbitrary markup appears outside it. --- ## Details `DOMImplementation.createDocumentType(qualifiedName, publicId, systemId, internalSubset)` validates only `qualifiedName` against the XML QName production. The remaining three arguments are stored as-is with no validation. The XMLSerializer emits `DocumentType` nodes as: ``` <!DOCTYPE name[ PUBLIC pubid][ SYSTEM sysid][ [internalSubset]]> ``` All fields are pushed into the output buffer verbatim — no escaping, no quoting added. **`internalSubset` injection:** The serializer wraps `internalSubset` with ` [` and `]`. A value containing `]>` closes the internal subset and the DOCTYPE declaration at the injection point. Any content after `]>` in `internalSubset` appears outside the DOCTYPE in the serialized output as raw XML markup. Reported by @TharVid (GHSA-f6ww-3ggp-fr8h). Affected: `@xmldom/xmldom` ≥ 0.9.0 via `createDocumentType` API; 0.8.x only via direct property write. **`publicId` injection:** The serializer emits `publicId` verbatim after `PUBLIC` with no quoting added. A value containing an injected system identifier (e.g., `"pubid" SYSTEM "evil"`) breaks the intended quoting context, injecting a fake SYSTEM entry into the serialized DOCTYPE declaration. Identified during internal security research. Affected: both branches, all versions back to 0.1.0. **`systemId` injection:** The serializer emits `systemId` verbatim. A value containing `>` terminates the DOCTYPE declaration early; content after `>` appears as raw XML markup outside the DOCTYPE context. Identified during internal security research. Affected: both branches, all versions back to 0.1.0. The parse path is safe: the SAX parser enforces the `PubidLiteral` and `SystemLiteral` grammar productions, which exclude the relevant characters, and the internal subset parser only accepts a subset it can structurally validate. The vulnerability is reachable only through programmatic `createDocumentType` calls with attacker-controlled arguments. --- ## Affected code **`lib/dom.js` — `createDocumentType` (lines 898–910):** ```js createDocumentType: function (qualifiedName, publicId, systemId, internalSubset) { validateQualifiedName(qualifiedName); // only qualifiedName is validated var node = new DocumentType(PDC); node.name = qualifiedName; node.nodeName = qualifiedName; node.publicId = publicId || ''; // stored verbatim node.systemId = systemId || ''; // stored verbatim node.internalSubset = internalSubset || ''; // stored verbatim node.childNodes = new NodeList(); return node; }, ``` **`lib/dom.js` — serializer DOCTYPE case (lines 2948–2964):** ```js case DOCUMENT_TYPE_NODE: var pubid = node.publicId; var sysid = node.systemId; buf.push(g.DOCTYPE_DECL_START, ' ', node.name); if (pubid) { buf.push(' ', g.PUBLIC, ' ', pubid); if (sysid && sysid !== '.') { buf.push(' ', sysid); } } else if (sysid && sysid !== '.') { buf.push(' ', g.SYSTEM, ' ', sysid); } if (node.internalSubset) { buf.push(' [', node.internalSubset, ']'); // internalSubset emitted verbatim } buf.push('>'); return; ``` --- ## PoC ### internalSubset injection ```js const { DOMImplementation, XMLSerializer } = require('@xmldom/xmldom'); const impl = new DOMImplementation(); const doctype = impl.createDocumentType( 'root', '', '', ']><injected/><![CDATA[' ); const doc = impl.createDocument(null, 'root', doctype); const xml = new XMLSerializer().serializeToString(doc); console.log(xml); // <!DOCTYPE root []><injected/><![CDATA[]><root/> // ^^^^^^^^^^ injected element outside DOCTYPE ``` ### publicId quoting context break ```js const { DOMImplementation, XMLSerializer } = require('@xmldom/xmldom'); const impl = new DOMImplementation(); const doctype = impl.createDocumentType( 'root', '"injected PUBLIC_ID" SYSTEM "evil"', '', '' ); const doc = impl.createDocument(null, 'root', doctype); console.log(new XMLSerializer().serializeToString(doc)); // <!DOCTYPE root PUBLIC "injected PUBLIC_ID" SYSTEM "evil"><root/> // quoting context broken — SYSTEM entry injected ``` ### systemId injection ```js const { DOMImplementation, XMLSerializer } = require('@xmldom/xmldom'); const impl = new DOMImplementation(); const doctype = impl.createDocumentType( 'root', '', '"sysid"><injected attr="pwn"/>', '' ); const doc = impl.createDocument(null, 'root', doctype); console.log(new XMLSerializer().serializeToString(doc)); // <!DOCTYPE root SYSTEM "sysid"><injected attr="pwn"/>><root/> // > in sysid closes DOCTYPE early; <injected/> appears as sibling element ``` --- ## Impact An application that programmatically constructs `DocumentType` nodes from user-controlled data and then serializes the document can emit a DOCTYPE declaration where the internal subset is closed early or where injected SYSTEM entities or other declarations appear in the serialized output. Downstream XML parsers that re-parse the serialized output and expand entities from the injected DOCTYPE declarations may be susceptible to XXE-class attacks if they enable entity expansion. --- ## Fix Applied > **⚠ Opt-in required.** Protection is not automatic. Existing serialization calls remain > vulnerable unless `{ requireWellFormed: true }` is explicitly passed. Applications that pass > untrusted data to `createDocumentType()` or write untrusted values directly to a > `DocumentType` node's `publicId`, `systemId`, or `internalSubset` properties should audit > all `serializeToString()` call sites and add the option. `XMLSerializer.serializeToString()` now accepts an options object as a second argument. When `{ requireWellFormed: true }` is passed, the serializer validates the `DocumentType` node's `publicId`, `systemId`, and `internalSubset` fields before emitting the DOCTYPE declaration and throws `InvalidStateError` if any field contains an injection sequence: - **`publicId`**: throws if non-empty and does not match the XML `PubidLiteral` production (XML 1.0 [12]) - **`systemId`**: throws if non-empty and does not match the XML `SystemLiteral` production (XML 1.0 [11]) - **`internalSubset`**: throws if it contains `]>` (which closes the internal subset and DOCTYPE declaration early) All three checks apply regardless of how the invalid value entered the node — whether via `createDocumentType` arguments or a subsequent direct property write. ### PoC — fixed path ```js const { DOMImplementation, XMLSerializer } = require('@xmldom/xmldom'); const impl = new DOMImplementation(); // internalSubset injection const dt1 = impl.createDocumentType('root', '', '', ']><injected/><![CDATA['); const doc1 = impl.createDocument(null, 'root', dt1); // Default (unchanged): verbatim — injection present console.log(new XMLSerializer().serializeToString(doc1)); // <!DOCTYPE root []><injected/><![CDATA[]><root/> // Opt-in guard: throws InvalidStateError try { new XMLSerializer().serializeToString(doc1, { requireWellFormed: true }); } catch (e) { console.log(e.name, e.message); // InvalidStateError: DocumentType internalSubset contains "]>" } ``` The guard also covers post-creation property writes: ```js const dt2 = impl.createDocumentType('root', '', ''); dt2.systemId = '"sysid"><injected attr="pwn"/>'; const doc2 = impl.createDocument(null, 'root', dt2); new XMLSerializer().serializeToString(doc2, { requireWellFormed: true }); // InvalidStateError: DocumentType systemId is not a valid SystemLiteral ``` ### Why the default stays verbatim The W3C DOM Parsing and Serialization spec §3.2.1.3 defines a `require well-formed` flag whose **default value is `false`**. With the flag unset, the spec permits verbatim serialization of DOCTYPE fields. Unconditionally throwing would be a behavioral breaking change with no spec justification. The opt-in `requireWellFormed: true` flag allows applications that require injection safety to enable strict mode without breaking existing deployments. ### Residual limitation `createDocumentType(qualifiedName, publicId, systemId[, internalSubset])` does not validate `publicId`, `systemId`, or `internalSubset` at creation time. This creation-time validation is a breaking change and is deferred to a future breaking release. When the default serialization path is used (without `requireWellFormed: true`), all three fields are still emitted verbatim. Applications that do not pass `requireWellFormed: true` remain exposed.

Source published
22 Apr 2026 20:19:12 UTC
Source modified
10 Sep 2026 03:50:44 UTC
Saved here
24 Oct 2026 13:17:17 UTC
Source terms
GitHub Advisory Database · CC-BY-4.0

Original source record · Cite this exact observation · Complete original publisher JSON

Source: osv · GitHub Advisory Database · CC-BY-4.0. The publisher's wording is preserved. ChangeIntel groups the information and labels changes. The JSON record includes additional source fields.

CVSS_V4 · CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N

Publisher rating · HIGH

ChangeIntel

An IT change radar: releases, security, known issues, retirements, documentation changes, and service status from public sources. Every item links to supporting evidence; dates and statuses can change after they are read.

Sources read 15 Nov 04:55 UTC · 250 of 275 readable · 246 fully read · documentation 468/469 current