How XML Node Injection Happens in JavaScript XML Parsing and How to Fix It
The Incident
In the project's package-lock.json, the dependency @xmldom/xmldom was pinned to version 0.8.12 — a version carrying CVE-2026-41672, a high-severity XML node injection flaw. The Trivy scanner flagged the package in the dependency tree, and Orbis AppSec automatically opened a pull request upgrading it to 0.8.13 (and adding a package.json overrides entry to lock the patched version across the entire dependency graph).
This post explains exactly what the vulnerability is, how comment serialization becomes an injection vector, and what the two-file fix actually does.
The Vulnerability Explained
XML Comments and the -- Problem
The XML specification (§2.5) states that a comment looks like this:
<!-- this is a comment -->
It also explicitly forbids the string -- (double hyphen) from appearing inside comment content, because the parser uses --> to close a comment and -- anywhere else is illegal — but many real-world parsers are lenient and will treat -- as ending the comment early.
@xmldom/xmldom 0.8.12 serialized comment nodes by taking the raw text content and wrapping it in <!-- / --> without first checking whether that content contained --. This means that if an application built a comment node from user-controlled input — for example:
// Vulnerable pattern using @xmldom/xmldom 0.8.12
const doc = new DOMParser().parseFromString('<root/>', 'text/xml');
const comment = doc.createComment(userInput); // userInput is attacker-controlled
doc.documentElement.appendChild(comment);
const serialized = new XMLSerializer().serializeToString(doc);
…and userInput was something like:
safe text --> <script>evil()</script> <!--
The serialized output would become:
<root><!-- safe text --> <script>evil()</script> <!----></root>
The --> in the comment content closes the comment early, and <script>evil()</script> is now a real XML element in the serialized document — not comment text.
Why This Is Dangerous in Practice
Any downstream consumer of the serialized XML — another XML parser, an XSLT processor, an XML-based access-control policy engine, a SOAP handler — will see the injected nodes as legitimate document content. Depending on the application, this can:
- Bypass XML-based authorization checks (e.g., injecting a
<role>admin</role>node) - Corrupt SOAP or SAML payloads processed by a security-sensitive service
- Introduce executable content in contexts where XML is further processed (e.g., SVG, XHTML)
- Break XML schema validation or cause denial-of-service in strict parsers
The vulnerable version in this project was 0.8.12, recorded in package-lock.json:
"node_modules/@xmldom/xmldom": {
"version": "0.8.12",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.12.tgz",
"integrity": "sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg=="
}
The Fix
Two Files, Two Complementary Changes
The pull request modifies exactly two files: package-lock.json and package.json. Each plays a distinct role.
1. package-lock.json — Pinning the Patched Version
The lock file update swaps the resolved tarball and its integrity hash from 0.8.12 to 0.8.13:
"node_modules/@xmldom/xmldom": {
- "version": "0.8.12",
- "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.12.tgz",
- "integrity": "sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==",
+ "version": "0.8.13",
+ "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz",
+ "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
The new integrity hash ensures npm ci will reject any tampered tarball — an important supply-chain safeguard.
2. package.json — The overrides Entry
Simply updating the lock file is not always enough. If another dependency in the tree declares @xmldom/xmldom as its own dependency with a range that resolves to 0.8.12, npm could install a second copy of the vulnerable version. The overrides field forces npm to use 0.8.13 for every occurrence of @xmldom/xmldom in the entire dependency tree:
- ]
+ ],
+ "overrides": {
+ "@xmldom/xmldom": "0.8.13"
+ }
}
This is the correct pattern for remediating transitive dependency vulnerabilities in npm projects: the lock file pins the direct resolution, and overrides enforces it transitively.
What Changed Inside @xmldom/xmldom 0.8.13
The upstream patch in 0.8.13 adds a guard in the comment serialization path that checks whether the comment's text content contains --. If it does, serialization throws an error rather than producing malformed — and potentially injectable — XML. Valid comment content (containing no --) is completely unaffected, which is why the PR description notes that "it only tightens handling of untrusted input and leaves valid inputs unaffected."
Prevention & Best Practices
1. Validate Comment Content Before Creating Comment Nodes
If your application constructs XML comment nodes from any external input, validate the content before passing it to createComment():
function safeCreateComment(doc, text) {
// XML comments must not contain '--' (double hyphen)
if (text.includes('--')) {
throw new Error('Invalid comment content: "--" is not allowed in XML comments');
}
return doc.createComment(text);
}
2. Use npm overrides for Transitive Vulnerabilities
When a vulnerable package is a transitive dependency (pulled in by another package, not directly by your code), use overrides in package.json to force the patched version:
"overrides": {
"@xmldom/xmldom": ">=0.8.13"
}
Using a range (>=0.8.13) rather than an exact version allows future patch releases to be picked up automatically.
3. Automate Dependency Scanning in CI
Add Trivy, Snyk, or npm audit to your CI pipeline so that newly published CVEs are caught before they reach production:
# Example GitHub Actions step
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
severity: 'HIGH,CRITICAL'
4. Keep package-lock.json in Version Control
A committed lock file means every developer and CI run installs exactly the same dependency versions. Without it, npm install may silently resolve to a vulnerable version even after you've patched your package.json.
5. Relevant Standards
- CWE-91: XML Injection — covers injection of malicious content into XML documents
- OWASP — XML Security Cheat Sheet: recommends validating all XML input and using schema validation
- XML Specification §2.5: defines the legal grammar for XML comments and explicitly prohibits
--in comment content
Key Takeaways
@xmldom/xmldom0.8.12 did not validate the--sequence in comment content, allowing a single injected string to break out of a comment node and introduce arbitrary XML elements into a serialized document.- The
package-lock.jsonchange alone is insufficient when the vulnerable package also appears as a transitive dependency; thepackage.jsonoverridesentry is what guarantees the patched version is used everywhere. - XML comment injection is a real, exploitable attack vector — not just a theoretical flaw — because serialized XML is often consumed by security-sensitive downstream systems (SAML processors, XSLT engines, SOAP handlers).
- Trivy detected this vulnerability statically from the version string in
package-lock.json, demonstrating that dependency-tree scanning catches issues even before a code-path trace is confirmed. - Always treat XML comment text as untrusted input and reject or escape
--before passing it to any XML serializer.
How Orbis AppSec Detected This
- Source: The
@xmldom/xmldompackage version recorded inpackage-lock.json(version0.8.12) — a value that enters the dependency tree at install time and may originate from any caller that passes user-controlled strings tocreateComment(). - Sink: The comment serialization path inside
@xmldom/xmldom'sXMLSerializerimplementation, which writes raw comment content between<!--and-->without validating the--sequence. - Missing control: No check for the
--substring in comment node content prior to serialization, violating the XML specification's §2.5 constraint. - CWE: CWE-91 — XML Injection
- Fix: Upgraded
@xmldom/xmldomfrom0.8.12to0.8.13inpackage-lock.jsonand added anoverridesentry inpackage.jsonto enforce the patched version across the full dependency tree.
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
CVE-2026-41672 is a sharp reminder that XML is not a safe serialization format by default — even something as innocuous-looking as a comment node can become an injection vector when library-level validation is absent. The @xmldom/xmldom 0.8.12 → 0.8.13 upgrade closes this specific path by enforcing the XML specification's prohibition on -- inside comment content.
For Node.js projects, the two-file fix pattern demonstrated here — updating package-lock.json and adding a package.json overrides entry — is the correct way to remediate transitive dependency vulnerabilities. Pair that with automated scanning in CI and you'll catch the next CVE before it ever ships.