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)

Prevention & Best Practices

To prevent XML parsing vulnerabilities in Node.js applications:

1. Use Maintained XML Parsers

If you need XML parsing in Node.js, use actively maintained libraries:
- @xmldom/xmldom: The community-maintained fork of xmldom with active security updates
- fast-xml-parser: High-performance parser with strict validation options
- xml2js: Popular parser with good validation capabilities

2. Enforce Strict XML Validation

Always configure parsers to reject malformed documents:

const { DOMParser } = require('@xmldom/xmldom');

const parser = new DOMParser({
  errorHandler: {
    warning: (msg) => { throw new Error(`XML Warning: ${msg}`); },
    error: (msg) => { throw new Error(`XML Error: ${msg}`); },
    fatalError: (msg) => { throw new Error(`XML Fatal: ${msg}`); }
  }
});

const doc = parser.parseFromString(xmlString, 'text/xml');

// Explicitly validate single root element
if (doc.documentElement.nextSibling) {
  throw new Error('XML document must have exactly one root element');
}

3. Implement Schema Validation

Use XSD (XML Schema Definition) or DTD (Document Type Definition) to enforce document structure:

const Ajv = require('ajv');
const ajv = new Ajv();

// Define expected XML structure as JSON Schema after parsing
const schema = {
  type: 'object',
  properties: {
    root: {
      type: 'object',
      required: ['expectedChild']
    }
  },
  required: ['root'],
  additionalProperties: false
};

4. Regular Dependency Audits

Use automated tools to catch vulnerable dependencies:

npm audit
npm audit fix

# Or use more comprehensive tools
npx snyk test
npx trivy fs .

5. Consider Alternative Data Formats

Evaluate whether JSON, YAML, or Protocol Buffers might be more appropriate than XML for your use case. Modern applications often find JSON sufficient and less prone to parsing vulnerabilities.

6. Apply Defense in Depth

Even with a secure parser:
- Validate XML content against expected schemas
- Sanitize extracted data before use
- Apply principle of least privilege to XML processing code
- Log and monitor XML parsing errors

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.

References

Frequently Asked Questions

What is XML Multiple Root Element Injection?

It's a vulnerability where an XML parser incorrectly accepts documents with multiple root elements, violating the XML 1.0 specification that requires exactly one root element. This allows attackers to craft malformed documents that bypass validation logic expecting well-formed XML.

How do you prevent XML Multiple Root Element Injection in Node.js?

Use actively maintained, spec-compliant XML parsers like @xmldom/xmldom (the maintained fork) or native DOMParser. Always validate that parsed XML has exactly one root element, and implement schema validation using XSD or similar mechanisms to enforce document structure.

What CWE is XML Multiple Root Element Injection?

CWE-91 (XML Injection, also known as Blind XPath Injection). This vulnerability allows attackers to manipulate XML structure in ways that bypass security controls, similar to how SQL injection manipulates database queries.

Is input sanitization enough to prevent XML Multiple Root Element Injection?

No. Input sanitization helps but isn't sufficient. You need a spec-compliant XML parser that rejects documents with multiple root elements at parse time. The parser itself must enforce XML 1.0 structural requirements before any application-level validation occurs.

Can static analysis detect XML Multiple Root Element Injection?

Yes. Dependency scanners like Trivy, Snyk, and npm audit can detect known vulnerable versions of XML parsers like xmldom 0.6.0. Static analysis tools can also identify usage patterns where untrusted XML is parsed without proper validation of the document structure.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #5342

Related Articles

critical

How Prototype Pollution happens in Node.js and how to fix it

A critical prototype pollution vulnerability was discovered in `worker/import-core.js`, where `request.json()` parsed untrusted HTTP request bodies without filtering dangerous keys like `__proto__` and `constructor`. An attacker could send a crafted JSON payload to corrupt the global `Object` prototype, potentially affecting every object in the application runtime. The fix replaces the unsafe parse with a JSON reviver function that strips these dangerous keys before any object is constructed.

critical

How Server-Side Template Injection happens in Node.js EJS and how to fix it

CVE-2022-29078 is a critical server-side template injection (SSTI) vulnerability in EJS versions prior to 3.1.7, where the `outputFunctionName` option is passed directly into generated code without sanitization, allowing attackers to execute arbitrary JavaScript on the server. The fix upgrades the EJS dependency from 2.7.4 to 3.1.7+ (resolved here as 6.0.1), eliminating the unsafe code generation path. Any Node.js application rendering EJS templates with user-influenced options is at risk of ful

high

How Prototype Pollution happens in JavaScript via defu and how to fix it

CVE-2026-35209 is a high-severity prototype pollution vulnerability in the `defu` JavaScript library (versions prior to 6.1.5), where a crafted `__proto__` key in the defaults argument can corrupt the global Object prototype. The fix upgrades `defu` from 6.1.4 to 6.1.5 in `pnpm-lock.yaml` and enforces the version via a workspace override, closing the attack surface in production code that depends on `defu` for deep object merging.

critical

How eval() Code Injection happens in JavaScript and how to fix it

A critical code injection vulnerability was discovered in `js/lib/jsencrypt.js` at line 195, where a direct `eval()` call executed a JavaScript string shim for the `process` object in browser environments. If an attacker could influence the string passed to `eval()`—through a compromised dependency, a man-in-the-middle attack, or supply chain tampering—they could achieve arbitrary JavaScript execution in any user's browser. The fix replaces the `eval()` call with the equivalent inline JavaScript

high

How Unsafe eval() in JavaScript Happens in React Components and How to Fix It

A high-severity code injection vulnerability was discovered in `TurnPlanner.tsx`, where the `parseInputExpr` function used JavaScript's `Function` constructor — effectively `eval()` — to evaluate user-provided mathematical expressions. The regex guard in place only checked for the presence of arithmetic operators, not whether the input was safe to execute, leaving the door open for arbitrary JavaScript injection. A targeted whitelist fix was applied to reject any input containing characters outs

critical

How Archive Path Traversal Happens in Node.js and How to Fix It

CVE-2026-53486 is a critical path traversal vulnerability in the Decompress library, where crafted archive entries can write files and symbolic links outside the intended extraction directory. This vulnerability was transitively introduced through `@vitest/browser` and related packages pinned at version 4.1.5, and was resolved by upgrading to 4.1.6 and 5.0.0-beta.3. Left unpatched, an attacker who controls an archive file processed by any downstream consumer of this dependency chain could overwr