CVE-2026-83608: xmldom DocType `name` Injection Bypasses RequireWellFormed Vulnerability
HIGHCVE-2026-83608 is a HIGH doctype `name` injection bypasses requirewellformed vulnerability (CWE-91: XML Injection (aka Blind XPath Injection)) affecting xmldom libraries in the npm ecosystem. The issue involves doctype `name` injection bypasses requirewellformed. Remediation is available by upgrading affected packages to version 0.8.15, 0.9.12.
CVE-2026-83608 at a Glance
How to Fix CVE-2026-83608
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.
npm install @xmldom/xmldom@0.8.15
npm install @xmldom/xmldom@0.9.12
npm install xmldom@Latest secure release
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
npm ls @xmldom/xmldom
How to Check if You Are Affected
To determine whether your Node.js application is affected by CVE-2026-83608, inspect your project dependencies using npm:
npm ls @xmldom/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.15), your application is affected.
Note: The npm ls @xmldom/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
CVE-2026-83608 Affected xmldom Versions
3 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
@xmldom/xmldom
|
npm |
≥ 0.7.0
< 0.8.15
|
0.8.15
|
|
@xmldom/xmldom
|
npm |
≥ 0.9.0
< 0.9.12
|
0.9.12
|
|
xmldom
|
npm |
≤ 0.6.0
|
|
References
Frequently Asked Questions
What is CVE-2026-83608?
Which xmldom versions are affected?
What version fixes CVE-2026-83608?
Is @xmldom/xmldom affected?
Is xmldom affected?
What CWE is associated with CVE-2026-83608?
How can I check npm dependencies for CVE-2026-83608?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-27p8-2357-5qqv",
"modified" : "2026-09-08T21:02:51Z",
"published" : "2026-09-08T21:02:51Z",
"aliases" : [ "CVE-2026-83608" ],
"summary" : "xmldom: DocType `name` Injection Bypasses requireWellFormed",
"details" : "## Summary\n\nThe `@xmldom/xmldom` serializer emits `DocumentType.name` verbatim into the\n`<!DOCTYPE …>` declaration with no well-formedness guard. GHSA-f6ww-3ggp-fr8h\n(CVE-2026-41674) hardened the serializer's `requireWellFormed` path for a\nDocumentType's sibling fields — `publicId`, `systemId`, and `internalSubset` —\nbut it did **not** add any check for `name`. A `>` (or whitespace) in the name\nterminates the doctype declaration early, letting the remaining characters\nbecome sibling markup in the serialized output.\n\nBecause `requireWellFormed: true` — the recommended mitigation for the prior\nxmldom injection CVEs — performs no validation on the DocType `name`, this is a\nbypass of that control, in the same family as the open element-name\n(GHSA-w2rr-34g9-rvrj) and attribute-name (GHSA-4w3w-2rp5-g8jm) name-injection advisories.\n\n## Details\n\nThe serializer's `DOCUMENT_TYPE_NODE` case runs the `requireWellFormed` block\nonly against `publicId`, `systemId`, and `internalSubset`, then pushes\n`n.name` directly into the buffer between the `<!DOCTYPE ` prefix and the\nclosing `>`:\n\n- 0.9.x (v0.9.10, `bb7a085`):\n [serializer DocType case, `lib/dom.js#L3256-L3283`](https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom.js#L3256-L3283)\n — the `requireWellFormed` block ([#L3259-L3269](https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom.js#L3259-L3269))\n validates `publicId`/`systemId`/`internalSubset` but not `name`, which is\n emitted verbatim at [#L3270](https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom.js#L3270).\n- 0.8.x (v0.8.13, `e5c1480`):\n [serializer DocType case, `lib/dom.js#L1914-L1946`](https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/dom.js#L1914-L1946)\n — same structure; `name` is emitted verbatim at [#L1928](https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/dom.js#L1928).\n- unscoped `xmldom` (v0.6.0, `c80a161`):\n [`lib/dom.js#L1105`](https://github.com/xmldom/xmldom/blob/c80a161172cc4d8733583bf0cf59abfa589f6d9e/lib/dom.js#L1105)\n emits `node.name` verbatim; this line predates `requireWellFormed`, so there\n is no well-formedness path at all.\n\n### Enabling write paths\n\n`DocumentType.name` is a plain, writable own-property, so the enabling vector\ndiffers by line:\n\n- **0.9.x** — `createDocumentType()` validates the name via\n `validateQualifiedName` ([`lib/dom.js#L925-L936`](https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom.js#L925-L936),\n validation at [#L926](https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom.js#L926)),\n so the deliverable vector is a **direct property write**\n (`dt.name = 'html><script>…'`) to the unguarded own-property.\n- **0.8.x** — `createDocumentType()` does **not** validate the name\n ([`lib/dom.js#L456-L464`](https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/dom.js#L456-L464)),\n so the malicious name is reachable directly through `createDocumentType()` as\n well as via direct property write.\n- **unscoped `xmldom` (<= 0.6.0)** — `createDocumentType()` does not validate\n the name ([`lib/dom.js#L286`](https://github.com/xmldom/xmldom/blob/c80a161172cc4d8733583bf0cf59abfa589f6d9e/lib/dom.js#L286)),\n same as 0.8.x.\n\nThis is the same structural root cause the sibling name-injection advisories\nshare: the serializer's `requireWellFormed` path validates content delimiters\nbut no name field, and every name-like field is a plain writable property, so\nmutation / direct property-write bypasses any creation-time check.\n\n### Root Cause\n\n1. The serializer's `requireWellFormed` DocType block checks `publicId`,\n `systemId`, and `internalSubset` (the fields hardened by GHSA-f6ww-3ggp-fr8h)\n but has no check for `name`.\n2. `DocumentType.name` is a plain writable own-property; on 0.8.x and the\n unscoped package `createDocumentType()` does not validate it either.\n3. The serializer emits `name` directly between the doctype delimiters:\n `<!DOCTYPE ${name}…>`.\n\n## Proof of Concept\n\nRun against `@xmldom/xmldom` v0.9.10 (commit `bb7a085`):\n\n```javascript\nconst { DOMImplementation, XMLSerializer, DOMParser } = require('@xmldom/xmldom');\n\nconst impl = new DOMImplementation();\nconst serializer = new XMLSerializer();\n\n// 0.9.x createDocumentType validates the name, so overwrite it via direct property write\nconst dt = impl.createDocumentType('html', '', '');\ndt.name = 'html><script xmlns=\"http://www.w3.org/1999/xhtml\">alert(1)</script';\nconst doc = impl.createDocument(null, 'r', dt);\n\nconst output = serializer.serializeToString(doc, { requireWellFormed: true });\nconsole.log(output);\n// Output: <!DOCTYPE html><script xmlns=\"http://www.w3.org/1999/xhtml\">alert(1)</script><r/>\n//\n// requireWellFormed: true did NOT prevent the injection (no exception thrown).\n// The injected <script> is well-formed XHTML that a browser would execute.\n```\n\nConfirmed runtime behavior:\n\n- **0.9.x** — `createDocumentType()` rejects the malicious name at creation\n (`InvalidCharacterError`); a direct write to `dt.name` bypasses that, and\n `serializeToString(…, { requireWellFormed: true })` emits the breakout with no\n exception.\n- **Re-parse confirmation** — re-parsing the output shows the injected\n `<script>` is a real second top-level element originating entirely from the\n DocType name: the parser rejects it with\n `HierarchyRequestError: Only one element can be added and only after doctype`.\n A comment-injection variant (`dt.name = 'html><!--INJECTED--'`, output\n `<!DOCTYPE html><!--INJECTED--><r/>`) re-parses cleanly and the injected\n comment node is enumerable, confirming the injected node is structurally live.\n- **0.8.x** (v0.8.13, `e5c1480`) — `createDocumentType('html><script>…', '', '')`\n accepts the malicious name directly (no creation-time validation), and\n `serializeToString(doc, null, null, { requireWellFormed: true })` produces\n `<!DOCTYPE html><script>alert(1)</script><r/>` with no exception.\n\nA browser reproduction does not apply: browsers keep `DocumentType.name`\n`readonly`, so the direct-write vector cannot be reproduced in a browser DOM.\nThe injection is specific to xmldom exposing `name` as writable and serializing\nit without a guard.\n\n## Impact\n\nApplications that build a `DocumentType` node with an attacker-influenced\n`name` — via direct property write on any affected line, or via\n`createDocumentType()` on 0.8.x and the unscoped package — and serialize the\ndocument are vulnerable to XML/markup injection:\n\n- **XML structure injection** — breaking out of the `<!DOCTYPE …>` declaration\n to inject arbitrary sibling elements, comments, or additional markup into the\n output.\n- **XSS via XHTML** — if the serialized output is served as XHTML or processed\n by a browser-based XML parser, an injected `<script>` element (in the XHTML\n namespace) executes.\n- **requireWellFormed bypass** — applications that adopted\n `requireWellFormed: true` as a mitigation for the prior injection CVEs\n (including the sibling DocType fields fixed by GHSA-f6ww-3ggp-fr8h) remain\n vulnerable through the DocType `name`.\n\n## Fix Applied\n\nUnder `requireWellFormed`, the serializer validates the DocType `name` as a well-formed XML\n`Name` and throws `InvalidStateError` when it is not — matching the sibling\n`publicId`/`systemId`/`internalSubset` checks. Non-breaking and opt-in; ships on both maintained versions. No\ncreation-time change is made: 0.9.x already validates the name at `createDocumentType`, and the\n0.8.x/unscoped creation gap cannot be closed without a breaking change, so it is left\nunfixed. See the [XML `Name` production](https://www.w3.org/TR/xml/#NT-Name).\n> **⚠ Opt-in required.** Protection is not automatic. Existing serialization calls remain\n> vulnerable unless `{ requireWellFormed: true }` is explicitly passed. Applications that\n> serialize untrusted DOM content should audit all `serializeToString()` call sites and add it.\n\n### Proof of Concept - fixed path\n\n```javascript\nconst { DOMImplementation, XMLSerializer } = require('@xmldom/xmldom');\n\nconst impl = new DOMImplementation();\nconst serializer = new XMLSerializer();\n\nconst dt = impl.createDocumentType('html', '', '');\ndt.name = 'html><script xmlns=\"http://www.w3.org/1999/xhtml\">alert(1)</script';\nconst doc = impl.createDocument(null, 'r', dt);\n\n// Default path: the ill-formed name is still emitted verbatim (injection present).\nconsole.log(serializer.serializeToString(doc));\n// <!DOCTYPE html><script xmlns=\"http://www.w3.org/1999/xhtml\">alert(1)</script><r/>\n\n// Opt-in path: serialization throws instead of emitting the breakout.\nserializer.serializeToString(doc, { requireWellFormed: true });\n// throws InvalidStateError\n```\n\n### Why the default stays verbatim\n\nW3C DOM Parsing's require-well-formed flag defaults to false, and the browser\n`XMLSerializer` emits the name verbatim in that default mode. Unconditionally\nthrowing would be an unjustified breaking change against that specified default,\nso the guard is opt-in behind `{ requireWellFormed: true }`.\n\n### Residual limitation\n\nThe default serialization path still emits the ill-formed DocType `name`\nverbatim; protection applies only when `requireWellFormed: true` is passed. No\ncreation-time validation is added for the DocType `name`: 0.9.x already validates\nat `createDocumentType`, and the 0.8.x/unscoped creation gap is left unfixed — it\ncannot be closed without a breaking change.",
"severity" : [ {
"type" : "CVSS_V4",
"score" : "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"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "npm",
"name" : "@xmldom/xmldom"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0.7.0"
}, {
"fixed" : "0.8.15"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 0.8.14"
}
}, {
"package" : {
"ecosystem" : "npm",
"name" : "@xmldom/xmldom"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0.9.0"
}, {
"fixed" : "0.9.12"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 0.9.11"
}
}, {
"package" : {
"ecosystem" : "npm",
"name" : "xmldom"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"last_affected" : "0.6.0"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/xmldom/xmldom/security/advisories/GHSA-27p8-2357-5qqv"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-83608"
}, {
"type" : "WEB",
"url" : "https://github.com/xmldom/xmldom/pull/1071"
}, {
"type" : "WEB",
"url" : "https://github.com/xmldom/xmldom/pull/1072"
}, {
"type" : "WEB",
"url" : "https://github.com/xmldom/xmldom/commit/57aec90ac57b4408ae7c5d1746bf2a693b5ed90e"
}, {
"type" : "WEB",
"url" : "https://github.com/xmldom/xmldom/commit/85f12eb4d14b44de33216cfb72b50af4d24e9fdd"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/xmldom/xmldom"
}, {
"type" : "WEB",
"url" : "https://github.com/xmldom/xmldom/releases/tag/0.8.15"
}, {
"type" : "WEB",
"url" : "https://github.com/xmldom/xmldom/releases/tag/0.9.12"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-91" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-09-08T21:02:51Z",
"nvd_published_at" : "2026-09-01T15:17:38Z"
}
}