CVE-2026-83607: xmldom Element Name Injection Vulnerability

HIGH
Advisory Identifier: GHSA-w2rr-34g9-rvrj
CWE-91
Published
Advisory Summary

CVE-2026-83607 is a HIGH element name injection vulnerability (CWE-91: XML Injection (aka Blind XPath Injection)) affecting xmldom libraries in the npm ecosystem. The issue involves element name injection via createelement() bypasses requirewellformed. Remediation is available by upgrading affected packages to version 0.9.11, 0.8.14.

CVE-2026-83607 at a Glance

CVE Identifier CVE-2026-83607
GHSA Identifier GHSA-w2rr-34g9-rvrj
Severity HIGH
CVSS v3.1 Base Score N/A
CWE Classification CWE-91: XML Injection (aka Blind XPath Injection)
Affected Product xmldom
Package Ecosystem npm
Published Date
Last Updated

How to Fix CVE-2026-83607

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.

@xmldom/xmldom fixed: 0.9.11
npm install @xmldom/xmldom@0.9.11
@xmldom/xmldom fixed: 0.8.14
npm install @xmldom/xmldom@0.8.14
xmldom fixed: Latest secure release
npm install xmldom@Latest secure release
Verify Direct and Transitive Dependencies

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-83607, 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.11), 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

## Summary `Document.createElement()` in `@xmldom/xmldom` accepts arbitrary strings as the `tagName` parameter with zero validation. The serializer emits the tag name verbatim into XML/HTML output. Critically, the `requireWellFormed: true` serializer option — the recommended mitigation from CVE-2026-41672, CVE-2026-41674, and CVE-2026-34601 — did NOT catch this, making it a bypass of the existing security controls. An attacker who controls the element name string can inject arbitrary attributes (including event handlers) into the serialized output, leading to XSS when the output is consumed by a browser or downstream parser. ## Details `Document.createElement()` accepts any string as `tagName` and stores it directly on the element node without validation. When the document is later serialized via `XMLSerializer.serializeToString()`, the serializer emits the `tagName` verbatim into the output. The XML specification requires element names to conform to the `Name` production. The existing `createAttributeNS()` and `createElementNS()` methods validate qualified names against an anchored name/`QName` pattern, but `createElement()` bypasses this entirely, and the `requireWellFormed: true` serializer path performed no element-name validation — rendering it ineffective against this vector. ### Root Cause 1. `createElement()` stores the raw `tagName` string without any validation. 2. The serializer's `requireWellFormed` code path did not validate element names against the XML `Name`/`QName` production. 3. The serializer emits `tagName` directly into angle brackets: `<${tagName}...>`. ## Proof of Concept ```js const { DOMImplementation, XMLSerializer } = require('@xmldom/xmldom'); const impl = new DOMImplementation(); const serializer = new XMLSerializer(); const doc = impl.createDocument(null, 'root', null); // Inject an element whose "name" contains attributes with an XSS payload const el = doc.createElement('img src=x onerror="alert(1)"'); doc.documentElement.appendChild(el); const output = serializer.serializeToString(doc, { requireWellFormed: true }); console.log(output); // <root><img src=x onerror="alert(1)"/></root> // // A browser parsing this HTML will execute alert(1). // requireWellFormed: true did NOT prevent the injection. ``` ## Impact Applications that use `@xmldom/xmldom` to construct DOM trees and serialize them to XML/HTML are vulnerable to injection attacks if any part of an element name originates from user input. This includes: - **Cross-Site Scripting (XSS)**: Injecting event handler attributes (`onerror`, `onclick`, etc.) into HTML output consumed by browsers. - **XML injection**: Breaking XML document structure by injecting closing tags, new elements, or processing instructions through the element name. - **Security control bypass**: Applications that adopted `requireWellFormed: true` as a mitigation for CVE-2026-41672 / 41674 / 34601 remained vulnerable through this vector. `@xmldom/xmldom` can also be used inside browsers, where it mirrors the DOM API. Unlike the browser's `createElement()`, which rejects an invalid name with `InvalidCharacterError`, xmldom accepts it — developers may assume the same safety and skip validation. ## Fix Applied > **⚠ Opt-in required.** Protection is not automatic. Existing serialization calls remain > vulnerable unless `{ requireWellFormed: true }` is explicitly passed. Applications that > serialize untrusted DOM content should audit all `serializeToString()` call sites and add it. When `{ requireWellFormed: true }` is passed, the serializer now validates each element's serialized qualified name against the XML `QName` production and throws `InvalidStateError` before emitting the start tag. This also covers the **namespace-prefix** sub-vector: an invalid prefix surfaces either in the element qualified name (`PREFIX:local`) or in a synthesized `xmlns:PREFIX` declaration, and both are QName-checked. Fixed under `requireWellFormed: true` in `@xmldom/xmldom` **0.9.11** and **0.8.14**. Default serialization is unchanged. ### PoC — fixed path ```js const { DOMImplementation, XMLSerializer } = require('@xmldom/xmldom'); const doc = new DOMImplementation().createDocument(null, 'root', null); doc.documentElement.appendChild(doc.createElement('img src=x onerror="alert(1)"')); // Default (unchanged): verbatim — injection present console.log(new XMLSerializer().serializeToString(doc)); // <root><img src=x onerror="alert(1)"/></root> // Opt-in guard: throws InvalidStateError before serializing try { new XMLSerializer().serializeToString(doc, { requireWellFormed: true }); } catch (e) { console.log(e.name, e.message); // InvalidStateError: The element name "img src=x onerror="alert(1)"" is not a valid XML QName } ``` ### Why the default stays verbatim The W3C DOM Parsing and Serialization spec defines a `require well-formed` flag whose **default value is `false`**. With the flag unset, the serializer emits element names verbatim, matching the `XMLSerializer` behavior of Chrome, Firefox, and Safari. Unconditionally throwing would be a behavioral breaking change with no spec justification; the opt-in `requireWellFormed: true` flag lets applications that require injection safety enable strict mode without breaking existing code. ### Residual limitation `createElement(tagName)` does not validate `tagName` at creation time. Enforcing an `InvalidCharacterError` for invalid names unconditionally at creation time is a breaking change and is deferred to the next breaking release. When the default serialization path is used (without `requireWellFormed: true`), invalid element names are still emitted verbatim; applications that do not pass `requireWellFormed: true` remain exposed. Creation-time validation is tracked in a public issue on the next breaking-release milestone (filed at publication — issue link to be added), targeting the next breaking release.

CVE-2026-83607 Affected xmldom Versions

3 component(s)
Fixed Versions:
0.9.110.8.14
Product / Package Ecosystem Affected Versions Fixed Version
@xmldom/xmldom
npm
≥ 0.9.0
< 0.9.11
0.9.11
@xmldom/xmldom
npm
≥ 0.7.0
< 0.8.14
0.8.14
xmldom
npm
≤ 0.6.0

References

Frequently Asked Questions

What is CVE-2026-83607?
CVE-2026-83607 is a security vulnerability in xmldom involving Element name injection via createElement() bypasses requireWellFormed under CWE-91: XML Injection (aka Blind XPath Injection).
Which xmldom versions are affected?
Affected versions include releases prior to 0.9.11, 0.8.14 across affected packages: @xmldom/xmldom, xmldom.
What version fixes CVE-2026-83607?
Upgrading to 0.9.11, 0.8.14 (or the latest patched release for your respective branch) resolves the vulnerability.
Is @xmldom/xmldom affected?
Yes, @xmldom/xmldom is affected in releases prior to 0.9.11, 0.8.14. Upgrade your dependency to 0.9.11, 0.8.14 or later.
Is xmldom affected?
Yes, xmldom is affected in releases prior to Latest secure release. Upgrade your dependency to Latest secure release or later.
What CWE is associated with CVE-2026-83607?
CVE-2026-83607 is classified under CWE-91: XML Injection (aka Blind XPath Injection) (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.).
How can I check npm dependencies for CVE-2026-83607?
Run the dependency tree command: 'npm ls @xmldom/xmldom'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-w2rr-34g9-rvrj",
  "modified" : "2026-09-08T20:31:08Z",
  "published" : "2026-09-08T20:31:08Z",
  "aliases" : [ "CVE-2026-83607" ],
  "summary" : "xmldom: Element name injection via createElement() bypasses requireWellFormed",
  "details" : "## Summary\n\n`Document.createElement()` in `@xmldom/xmldom` accepts arbitrary strings as the `tagName` parameter with zero validation. The serializer emits the tag name verbatim into XML/HTML output. Critically, the `requireWellFormed: true` serializer option — the recommended mitigation from CVE-2026-41672, CVE-2026-41674, and CVE-2026-34601 — did NOT catch this, making it a bypass of the existing security controls.\n\nAn attacker who controls the element name string can inject arbitrary attributes (including event handlers) into the serialized output, leading to XSS when the output is consumed by a browser or downstream parser.\n\n## Details\n\n`Document.createElement()` accepts any string as `tagName` and stores it directly on the element node without validation. When the document is later serialized via `XMLSerializer.serializeToString()`, the serializer emits the `tagName` verbatim into the output.\n\nThe XML specification requires element names to conform to the `Name` production. The existing `createAttributeNS()` and `createElementNS()` methods validate qualified names against an anchored name/`QName` pattern, but `createElement()` bypasses this entirely, and the `requireWellFormed: true` serializer path performed no element-name validation — rendering it ineffective against this vector.\n\n### Root Cause\n\n1. `createElement()` stores the raw `tagName` string without any validation.\n2. The serializer's `requireWellFormed` code path did not validate element names against the XML `Name`/`QName` production.\n3. The serializer emits `tagName` directly into angle brackets: `<${tagName}...>`.\n\n## Proof of Concept\n\n```js\nconst { DOMImplementation, XMLSerializer } = require('@xmldom/xmldom');\n\nconst impl = new DOMImplementation();\nconst serializer = new XMLSerializer();\nconst doc = impl.createDocument(null, 'root', null);\n\n// Inject an element whose \"name\" contains attributes with an XSS payload\nconst el = doc.createElement('img src=x onerror=\"alert(1)\"');\ndoc.documentElement.appendChild(el);\n\nconst output = serializer.serializeToString(doc, { requireWellFormed: true });\nconsole.log(output);\n// <root><img src=x onerror=\"alert(1)\"/></root>\n//\n// A browser parsing this HTML will execute alert(1).\n// requireWellFormed: true did NOT prevent the injection.\n```\n\n## Impact\n\nApplications that use `@xmldom/xmldom` to construct DOM trees and serialize them to XML/HTML are vulnerable to injection attacks if any part of an element name originates from user input. This includes:\n\n- **Cross-Site Scripting (XSS)**: Injecting event handler attributes (`onerror`, `onclick`, etc.) into HTML output consumed by browsers.\n- **XML injection**: Breaking XML document structure by injecting closing tags, new elements, or processing instructions through the element name.\n- **Security control bypass**: Applications that adopted `requireWellFormed: true` as a mitigation for CVE-2026-41672 / 41674 / 34601 remained vulnerable through this vector.\n\n`@xmldom/xmldom` can also be used inside browsers, where it mirrors the DOM API. Unlike the browser's `createElement()`, which rejects an invalid name with `InvalidCharacterError`, xmldom accepts it — developers may assume the same safety and skip validation.\n\n## Fix Applied\n\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\nWhen `{ requireWellFormed: true }` is passed, the serializer now validates each element's serialized qualified name against the XML `QName` production and throws `InvalidStateError` before emitting the start tag. This also covers the **namespace-prefix** sub-vector: an invalid prefix surfaces either in the element qualified name (`PREFIX:local`) or in a synthesized `xmlns:PREFIX` declaration, and both are QName-checked.\n\nFixed under `requireWellFormed: true` in `@xmldom/xmldom` **0.9.11** and **0.8.14**. Default serialization is unchanged.\n\n### PoC — fixed path\n\n```js\nconst { DOMImplementation, XMLSerializer } = require('@xmldom/xmldom');\n\nconst doc = new DOMImplementation().createDocument(null, 'root', null);\ndoc.documentElement.appendChild(doc.createElement('img src=x onerror=\"alert(1)\"'));\n\n// Default (unchanged): verbatim — injection present\nconsole.log(new XMLSerializer().serializeToString(doc));\n// <root><img src=x onerror=\"alert(1)\"/></root>\n\n// Opt-in guard: throws InvalidStateError before serializing\ntry {\n  new XMLSerializer().serializeToString(doc, { requireWellFormed: true });\n} catch (e) {\n  console.log(e.name, e.message);\n  // InvalidStateError: The element name \"img src=x onerror=\"alert(1)\"\" is not a valid XML QName\n}\n```\n\n### Why the default stays verbatim\n\nThe W3C DOM Parsing and Serialization spec defines a `require well-formed` flag whose **default value is `false`**. With the flag unset, the serializer emits element names verbatim, matching the `XMLSerializer` behavior of Chrome, Firefox, and Safari. Unconditionally throwing would be a behavioral breaking change with no spec justification; the opt-in `requireWellFormed: true` flag lets applications that require injection safety enable strict mode without breaking existing code.\n\n### Residual limitation\n\n`createElement(tagName)` does not validate `tagName` at creation time. Enforcing an `InvalidCharacterError` for invalid names unconditionally at creation time is a breaking change and is deferred to the next breaking release. When the default serialization path is used (without `requireWellFormed: true`), invalid element names are still emitted verbatim; applications that do not pass `requireWellFormed: true` remain exposed.\n\nCreation-time validation is tracked in a public issue on the next breaking-release milestone (filed at publication — issue link to be added), targeting the next breaking release.",
  "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.0"
      }, {
        "fixed" : "0.9.11"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 0.9.10"
    }
  }, {
    "package" : {
      "ecosystem" : "npm",
      "name" : "@xmldom/xmldom"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0.7.0"
      }, {
        "fixed" : "0.8.14"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 0.8.13"
    }
  }, {
    "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-w2rr-34g9-rvrj"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-83607"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/xmldom/xmldom/pull/1043"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/xmldom/xmldom/pull/1050"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/xmldom/xmldom/commit/cba1321218b069182695813fa7565653708e172e"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/xmldom/xmldom/commit/d8212e632507eaf1d9f609657dd4c56abeb12d44"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/xmldom/xmldom"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/xmldom/xmldom/releases/tag/0.8.14"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/xmldom/xmldom/releases/tag/0.9.11"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-91" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-09-08T20:31:08Z",
    "nvd_published_at" : "2026-09-01T15:17:38Z"
  }
}