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
•Technically reviewed by Anupam Mediratta•Published August 27, 2026•Reviewed 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

CVE-2026-4800: lodash Template Imports Allow Code Execution

CVE-2026-4800 affects lodash's `_.template()` templating API, where untrusted input reaching the `imports` option can lead to arbitrary code execution. The fix was shipped as a dependency upgrade from lodash 4.17.21 to 4.18.1 in the project's lockfile, though the PR itself notes it was never verified against the actual code paths in this repository.

critical

proxy-addr 2.0.7 IP Spoofing: CVE-2026-90711 Trust Bypass

A critical vulnerability in proxy-addr 2.0.7 allowed attackers to spoof client IP addresses by manipulating X-Forwarded-For headers when the trust chain evaluation contained specific misconfigurations. The fix in version 2.0.8 hardens the trust evaluation logic to prevent IP address falsification in Express.js applications relying on this common middleware dependency.

high

TweenMax `_applyCycle` Prototype Pollution via vars.cycle Keys

A bundled copy of the TweenMax animation library copied attacker-influenceable `vars.cycle` property names straight onto a tween configuration object using an unguarded `for...in` loop, so a key named `__proto__`, `constructor`, or `prototype` was written through to the object's prototype chain. The fix adds an explicit key denylist to both copies of the `_applyCycle` helper so those three names are skipped during the merge. No CVE or GHSA is assigned; the issue is tracked as CWE-1321 (Improperl

high

picomatch 2.3.1 ReDoS: Extglob Pattern Catastrophic Backtracking

picomatch versions below 2.3.2, 3.0.2, or 4.0.4 contain a Regular Expression Denial of Service vulnerability in extglob pattern parsing. An attacker can cause catastrophic backtracking with patterns containing nested alternations and quantifiers, freezing any Node.js process that evaluates untrusted glob expressions.

critical

pet-window.js Dynamic Code Evaluation: CWE-94 Hardening via Number

The pet-window module constructed dynamic JavaScript by embedding raw configuration values into code strings. An attacker with local access could inject arbitrary JavaScript by modifying stored configuration. The fix replaces string interpolation with explicit Number() coercion and NaN validation for all numeric configuration parameters.

high

sanitizeUnicodeInput(): Fullwidth U+ Bypasses Codepoint Validation

The `sanitizeUnicodeInput()` helper used by the project character-range settings screen rewrote `U+` prefixes to `0x` and called `parseInt()`, but never normalized its argument first. Compatibility-equivalent forms such as fullwidth `U+`, superscript digits, or mathematical alphanumerics never matched the `/U\+/gi` regex, fell through to the `else return inputString` branch, and were handed back to callers verbatim as "sanitized" values. The fix inserts a `String.prototype.normalize('NFKC')` pas