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:
- The application receives XML input and parses it with xmldom 0.6.0
- Security middleware validates the first root element and its children
- The attacker includes a second root element containing malicious payloads
- Application logic processes the DOM tree, encountering the unvalidated second root element
- 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:
- Zero attack surface: By removing xmldom completely, there's no vulnerable parser in the dependency tree to exploit
- No version pinning risks: Rather than pinning to a "safe" version that might have future vulnerabilities, the dependency is gone
- 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.