CVE-2026-83618: xmldom RequireWellFormed DocType PublicId/systemId Validation Is Vulnerability
HIGHCVE-2026-83618 is a HIGH requirewellformed doctype publicid/systemid validation is vulnerability (CWE-91: XML Injection (aka Blind XPath Injection)) affecting xmldom libraries in the npm ecosystem. The issue involves requirewellformed doctype publicid/systemid validation is bypassable via an embedded line terminator. Remediation is available by upgrading affected packages to version 0.9.12.
CVE-2026-83618 at a Glance
How to Fix CVE-2026-83618
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.9.12
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-83618, 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.9.12), 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-83618 Affected xmldom Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
@xmldom/xmldom
|
npm |
≥ 0.9.10
< 0.9.12
|
0.9.12
|
References
Frequently Asked Questions
What is CVE-2026-83618?
Which xmldom versions are affected?
What version fixes CVE-2026-83618?
Is @xmldom/xmldom affected?
What CWE is associated with CVE-2026-83618?
How can I check npm dependencies for CVE-2026-83618?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-vr34-hp96-76pp",
"modified" : "2026-09-08T21:02:25Z",
"published" : "2026-09-08T21:02:25Z",
"aliases" : [ "CVE-2026-83618" ],
"summary" : "xmldom: requireWellFormed DocType publicId/systemId validation is bypassable via an embedded line terminator",
"details" : "## Summary\n\nAn embedded line terminator bypasses the `requireWellFormed` serializer check for a `DocumentType`'s\npublicId and systemId. The check was added to fix GHSA-f6ww-3ggp-fr8h; an id whose first line is a\nvalid literal slips past it and is emitted verbatim into the `<!DOCTYPE …>` declaration, so the markup\nafter the line terminator breaks out into the surrounding document. Callers who enabled\n`requireWellFormed` to neutralize DocumentType injection remain exposed.\n\n## Details\n\n`publicId` and `systemId` are stored as raw values **including their surrounding quotes**, and the\n`PubidLiteral`/`SystemLiteral` productions include those quotes. The serializer validates them with\n`g.PubidLiteral_match.test(publicId)` and `g.SystemLiteral_match.test(systemId)`, where both matchers\nare `reg('^', …, '$')` and inherit the `m` flag from xmldom's shared regexp builder. Under `m`, `$`\nmatches at an interior line terminator, so a value such as `\"valid pubid\"\\n\"><!ENTITY …>` satisfies\nthe matcher on its first line (`\"valid pubid\"` is a complete `PubidLiteral`) and the whole value —\nincluding the post-newline breakout — is emitted after `PUBLIC`/`SYSTEM`.\n\n### Root Cause\n\n1. A shared regexp builder compiles anchored productions with the `m` flag.\n2. `^…$` under `m` are line anchors, not string anchors.\n3. A full-string validator built on such a production (`.test()`) accepts any string with one\n conforming line, so a complete, valid literal on the first line passes even though a line terminator\n and breakout markup follow. `PubidChar` excluding `<`/`>` does not prevent it — the breakout is\n appended *after* the literal, not embedded inside it.\n\nThe triggering line terminators are the ECMAScript `LineTerminator` set: U+000A, U+000D, U+2028, U+2029.\n\n## Affected Versions\n\nOnly `@xmldom/xmldom` 0.9.x is affected. The vulnerable matchers are built by `lib/grammar.js`'s\n`m`-flagged `reg()` builder, and the DocType `publicId`/`systemId` `requireWellFormed` check that\nconsumes them was introduced in 0.9.10 (the GHSA-f6ww-3ggp-fr8h fix); 0.9.10 and 0.9.11 carry it.\n`0.8.x` performs the same `requireWellFormed` check with inline, non-`m` regular expressions and is not\naffected. The unscoped `xmldom` package has no `grammar.js` and no `requireWellFormed` serializer, so\nthere is no check to bypass.\n\n## Proof of Concept\n\n```js\nconst { DOMImplementation, XMLSerializer } = require('@xmldom/xmldom');\nconst impl = new DOMImplementation();\n\n// publicId: complete literal on line 1, then newline + breakout\nconst dt = impl.createDocumentType('html', '\"valid pubid\"\\n\"><!ENTITY xxe SYSTEM \"file:///etc/passwd\">', '');\nconst doc = impl.createDocument(null, 'root', dt);\nconsole.log(new XMLSerializer().serializeToString(doc, { requireWellFormed: true }));\n// Observed (no throw):\n// <!DOCTYPE html PUBLIC \"valid pubid\"\n// \"><!ENTITY xxe SYSTEM \"file:///etc/passwd\">><root/>\n// Expected: InvalidStateError (publicId is not a valid PubidLiteral).\n// Control: a single-line invalid publicId (\"no-surrounding-quotes<>\") DOES throw InvalidStateError,\n// confirming the check is active and specifically bypassed by the line terminator.\n```\n\n## Impact\n\n- **Bypass of the GHSA-f6ww-3ggp-fr8h mitigation.** Applications that adopted `requireWellFormed:\n true` to neutralize DocumentType injection remain exposed.\n- **XML structure injection into the DOCTYPE**, including injected markup / entity declarations after\n the public or system identifier.\n\n## Fix Applied\n\nThe anchored `PubidLiteral`/`SystemLiteral` validators used by the `requireWellFormed`\nserializer no longer treat an interior line terminator as satisfying the `$` anchor, so a `publicId`\nor `systemId` containing any ECMAScript `LineTerminator` (U+000A, U+000D, U+2028, U+2029) is rejected\nwith `InvalidStateError`. Valid single-line identifiers serialize unchanged, and the default\nserialization path is unaffected.\n\n> **⚠ Opt-in required.** Protection is not automatic. Existing serialization calls remain vulnerable\n> unless `{ requireWellFormed: true }` is explicitly passed. Applications that serialize untrusted DOM\n> content should audit all `serializeToString()` call sites and add it.\n\n### Proof of Concept - fixed path\n\n```js\nconst { DOMImplementation, XMLSerializer } = require('@xmldom/xmldom');\nconst impl = new DOMImplementation();\nconst dt = impl.createDocumentType('html', '\"valid pubid\"\\n\"><!ENTITY xxe SYSTEM \"file:///etc/passwd\">', '');\nconst doc = impl.createDocument(null, 'root', dt);\n\n// Default path (requireWellFormed off) — unchanged, still emits verbatim:\nconsole.log(new XMLSerializer().serializeToString(doc));\n// <!DOCTYPE html PUBLIC \"valid pubid\"\n// \"><!ENTITY xxe SYSTEM \"file:///etc/passwd\">><root/>\n\n// Opt-in path — now throws instead of emitting the breakout:\nnew XMLSerializer().serializeToString(doc, { requireWellFormed: true });\n// InvalidStateError: DocumentType publicId is not a valid PubidLiteral\n```\n\n### Why the default stays verbatim\n\nThe W3C DOM Parsing \"require well-formed\" flag defaults to false, and a browser `XMLSerializer` emits\nthe DOCTYPE verbatim. Unconditionally throwing on a malformed `publicId`/`systemId` would be an\nunjustified breaking change to the default path, so the fix tightens only the opt-in\n`requireWellFormed` validator, matching browser and spec defaults.\n\n### Residual limitation\n\nThe guarantee holds only for callers that pass `{ requireWellFormed: true }`; the default\nserialization path still emits `publicId`/`systemId` verbatim. `publicId` and `systemId` are not\nvalidated at creation (`createDocumentType`) or on direct property assignment\n(`documentType.publicId = …`) — the WHATWG DOM specification places no well-formedness constraint on\nthese fields at creation time, so the serializer is the spec-aligned enforcement point.",
"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.9.10"
}, {
"fixed" : "0.9.12"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 0.9.11"
}
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/xmldom/xmldom/security/advisories/GHSA-vr34-hp96-76pp"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-83618"
}, {
"type" : "WEB",
"url" : "https://github.com/xmldom/xmldom/pull/1071"
}, {
"type" : "WEB",
"url" : "https://github.com/xmldom/xmldom/commit/7b2ec67e1750daadd0bb06c92e875e726544a362"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/xmldom/xmldom"
}, {
"type" : "WEB",
"url" : "https://github.com/xmldom/xmldom/releases/tag/0.9.12"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-625", "CWE-91" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-09-08T21:02:25Z",
"nvd_published_at" : "2026-09-01T15:17:40Z"
}
}