Back to Blog
critical SEVERITY6 min read

How XML Multiple Root Element Injection happens in Node.js and how to fix it

The foam3 project contained a critical vulnerability in xmldom version 0.6.0 that allowed attackers to create malformed XML documents with multiple root elements, violating the XML specification and potentially bypassing security validations. The fix removed the vulnerable xmldom dependency entirely from package.json and package-lock.json, eliminating the attack surface.

O
By Orbis AppSec
Published August 27, 2026Reviewed August 27, 2026

Answer Summary

CVE-2022-39353 is a critical XML parsing vulnerability in xmldom versions ≤0.6.0 that allows multiple root elements in a DOM tree, violating XML 1.0 specification which mandates exactly one root element. This can bypass security controls that assume well-formed XML. The fix removes xmldom 0.6.0 from the dependency tree entirely by deleting it from both package.json and package-lock.json, eliminating the vulnerable parser from the application.

Vulnerability at a Glance

cweCWE-91 (XML Injection)
fixRemove xmldom dependency from package.json and package-lock.json
riskMalformed XML can bypass validation logic and security controls
languageJavaScript/Node.js
root causexmldom 0.6.0 parser incorrectly accepts documents with multiple root elements
vulnerabilityXML Multiple Root Element Injection (CVE-2022-39353)

Introduction

The foam3 project's package-lock.json revealed a critical vulnerability: xmldom version 0.6.0 was allowing XML documents to contain multiple root elements, a clear violation of the XML 1.0 specification. This seemingly technical parsing quirk creates a serious security risk—when XML parsers accept malformed documents that validation logic assumes impossible, attackers gain a foothold to bypass security controls.

The vulnerable dependency was declared in package.json at line 11 as "xmldom": "^0.6.0" and resolved to version 0.6.0 in the dependency tree. This specific version of xmldom fails to enforce one of XML's most fundamental rules: a well-formed document must have exactly one root element.

The Vulnerability Explained

According to the XML 1.0 specification, every valid XML document must contain exactly one root element that contains all other elements. The xmldom 0.6.0 parser, however, incorrectly accepts documents like this:

<root1>
  <data>First document</data>
</root1>
<root2>
  <data>Second document</data>
</root2>

This malformed XML should be rejected immediately by any spec-compliant parser, but xmldom 0.6.0 processes it without error, creating a DOM tree with multiple top-level elements.

Why is this dangerous? Security validation logic often assumes XML is well-formed. Consider this attack scenario in the foam3 application:

  1. The application receives XML input and parses it with xmldom 0.6.0
  2. Security middleware validates the first root element and its children
  3. The attacker includes a second root element containing malicious payloads
  4. Application logic processes the DOM tree, encountering the unvalidated second root element
  5. The malicious payload bypasses security checks that only examined the first root

For example, if foam3 uses XML for configuration or data interchange, an attacker could inject:

<config>
  <allowedDomain>trusted.com</allowedDomain>
</config>
<config>
  <allowedDomain>attacker.com</allowedDomain>
  <adminAccess>true</adminAccess>
</config>

The validation logic might approve the first <config> block, but downstream code iterating through all root elements would process the attacker's second block, potentially granting unauthorized access.

The vulnerability exists because xmldom 0.6.0's parser implementation in its core parsing logic fails to enforce the single-root constraint. The package was also unmaintained—the last update to xmldom was in 2021, and the community had already migrated to the maintained fork @xmldom/xmldom.

The Fix

The fix takes a decisive approach: complete removal of the vulnerable dependency. Rather than upgrading to a patched version, the foam3 team removed xmldom entirely from the project.

Before (package.json:11-12):

"dependencies": {
  "container-query-polyfill": "^1.0.2",
  "xmldom": "^0.6.0"
}

After (package.json:11):

"dependencies": {
  "container-query-polyfill": "^1.0.2"
}

The corresponding changes in package-lock.json removed the entire xmldom dependency tree:

Before (package-lock.json:1258-1266):

"node_modules/xmldom": {
  "version": "0.6.0",
  "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.6.0.tgz",
  "integrity": "sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg==",
  "engines": {
    "node": ">=10.0.0"
  }
}

After: (entire block removed)

This fix is effective because:

  1. Zero attack surface: By removing xmldom completely, there's no vulnerable parser in the dependency tree to exploit
  2. No version pinning risks: Rather than pinning to a "safe" version that might have future vulnerabilities, the dependency is gone
  3. Forced architecture review: Removing the dependency forces developers to evaluate whether XML parsing is truly necessary and, if so, to choose a modern, maintained alternative

The PR description notes this change is "scoped to 2 files on the vulnerable path" and "only tightens handling of untrusted input." This indicates that either:
- The application wasn't actively using xmldom's functionality
- The XML parsing requirements could be met through other means (browser-native DOMParser, other libraries, or alternative data formats)

Key Takeaways

  • xmldom 0.6.0 specifically violates XML 1.0 spec by accepting multiple root elements, creating a security bypass opportunity in foam3's dependency tree
  • Complete dependency removal is sometimes better than upgrading: The foam3 team eliminated the attack surface entirely rather than switching to a patched version
  • Unmaintained parsers are ticking time bombs: xmldom hadn't been updated since 2021; always prefer actively maintained alternatives like @xmldom/xmldom
  • Parser-level validation failures cascade to application logic: When parsers accept malformed input, downstream security controls built on well-formedness assumptions fail
  • package-lock.json changes matter for security: The fix modified both package.json and package-lock.json to ensure the vulnerable version couldn't be reinstalled

How Orbis AppSec Detected This

  • Source: The xmldom dependency in package.json and package-lock.json
  • Sink: Any code path that would invoke xmldom's DOMParser to process untrusted XML input
  • Missing control: The xmldom 0.6.0 parser lacks enforcement of the XML 1.0 single-root-element requirement, allowing malformed documents to bypass validation
  • CWE: CWE-91 (XML Injection / Blind XPath Injection)
  • Fix: Complete removal of the xmldom dependency from both package.json and package-lock.json eliminates the vulnerable parser

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-2022-39353 in xmldom 0.6.0 demonstrates how parser-level specification violations create exploitable security gaps. By allowing multiple root elements in XML documents, this vulnerability enabled attackers to craft malformed documents that could bypass security validation logic expecting well-formed XML. The foam3 project's fix—complete removal of the vulnerable dependency—represents the most secure approach when a dependency is both vulnerable and potentially unnecessary.

For developers maintaining Node.js applications, this vulnerability underscores the importance of dependency hygiene: regular audits, preference for maintained libraries, and willingness to remove dependencies rather than accumulate technical debt. When XML parsing is necessary, use modern, spec-compliant parsers with strict validation, and always verify that parsed documents conform to expected structural constraints.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #5342

Related Articles

high

How Regular Expression Denial of Service (ReDoS) Happens in Node.js trim-newlines and How to Fix It

CVE-2021-33623 exposed a Regular Expression Denial of Service (ReDoS) vulnerability in the npm package `trim-newlines` versions 1.0.0 and earlier. The vulnerable `.end()` method used an inefficient regex pattern that could cause severe performance degradation when processing malicious input. Upgrading to version 4.0.1 patches the regex implementation and eliminates the attack surface.

critical

How CSS Injection via Weak Pattern Validation happens in Vue.js and how to fix it

A critical CSS injection vulnerability in `testpage/App.vue` allowed attackers to bypass weak HTML5 pattern validation and load malicious stylesheets. The fix replaces direct variable assignment with a hardened `setCustomStylesheetHref()` method using strict regex validation.

critical

How Unvalidated Dynamic Component Loading happens in TypeScript/Viewi and how to fix it

A critical vulnerability in Viewi's component loader allowed attackers to inject malicious JavaScript through compromised or MITM-attacked external component servers. The fix adds proper HTTP response validation before parsing dynamically fetched JSON components.

high

How Denial of Service via Crafted ZIP File happens in Node.js and how to fix it

CVE-2026-39244 is a high-severity denial of service vulnerability in the adm-zip npm package that allows attackers to crash Node.js applications by uploading maliciously crafted ZIP files. The fix upgrades adm-zip from version 0.5.16 to 0.6.0, which adds proper memory bounds checking to prevent excessive allocation during archive extraction.

critical

How prototype pollution happens in JavaScript AST traversal and how to fix it

A critical prototype pollution primitive was fixed in `src/traverse/estraverse` where visitor-supplied child keys were merged with `Object.assign(Object.create(this.__keys), visitor.keys)`. Because `Object.assign` uses assignment semantics, a key literally named `__proto__` reached the `Object.prototype` setter and rewired the prototype chain of the traversal key map instead of being stored as data. The fix replaces the merge with an object spread (`{ ...VisitorKeys, ...visitor.keys }`), which *

critical

How SQL injection happens in Python DuckDB view creation and how to fix it

A critical SQL injection flaw in `python/src/idx/api.py:265` built five DuckDB `CREATE VIEW` statements with Python f-strings, interpolating a filesystem path directly into SQL text. The fix replaces the interpolated path with a bound parameter (`read_parquet(?)`) and moves the view names into a hardcoded, non-interpolated statement map — eliminating any path where filenames or directory values can alter SQL structure.