Back to Blog
critical SEVERITY5 min read

How XML Entity Expansion happens in Node.js and how to fix it

A critical XML External Entity (XXE) vulnerability in `lib/xml2json.js` allowed attackers to trigger exponential memory consumption through nested entity expansion. The fix adds `strictEntities: true` to both SAX parser instances, disabling dangerous entity processing that could crash servers processing untrusted XML.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published September 7, 2026•Reviewed September 7, 2026

Answer Summary

XML External Entity (XXE) injection in Node.js occurs when SAX parsers process XML without disabling entity expansion. In `odata-csdl`'s `lib/xml2json.js`, the `sax.parser()` calls at lines 58 and 631 lacked `strictEntities` configuration, enabling Billion Laughs attacks. CWE-611 (Improper Restriction of XML External Entity Reference). Fix: Add `strictEntities: true` to parser options to block entity expansion entirely.

Vulnerability at a Glance

cweCWE-611
fixAdd `strictEntities: true` to both `sax.parser()` calls in `lib/xml2json.js`
riskDenial of Service via exponential memory consumption from malicious XML entities
languageJavaScript (Node.js)
root causeSAX parser instantiated without `strictEntities: true`, allowing unrestricted entity expansion
vulnerabilityXML External Entity (XXE) Injection

Introduction

In the odata-csdl repository—a library that converts CSDL XML to JSON for OData services—we discovered a critical XML External Entity (XXE) vulnerability lurking in lib/xml2json.js. The file's XML parsing logic relied on the popular sax npm package, but two specific instantiations of sax.parser() at lines 58 and 631 were missing a crucial security flag. With only { xmlns: true } passed as options, these parsers left the door wide open to entity expansion attacks that could crash any server processing untrusted XML input.

This vulnerability is particularly dangerous because odata-csdl sits in the data transformation layer—exactly where malicious payloads often enter enterprise systems. Developers using this library to process external XML feeds, user uploads, or third-party metadata would have had no defense against a classic "Billion Laughs" attack.

The Vulnerability Explained

The xml2json module creates two SAX parsers to handle XML processing: a preParser for initial validation and a main parser for the actual conversion. Both were initialized identically:

// VULNERABLE CODE (before fix)
const preParser = sax.parser(true, { xmlns: true });
// ...
const parser = sax.parser(true, { xmlns: true });

The sax.parser(strict, options) function takes a strictness boolean and an options object. The xmlns: true option enables namespace awareness, but critically, it does not restrict entity expansion. Without strictEntities: true, the parser processes internal entity declarations like these:

<?xml version="1.0"?>
<!DOCTYPE lolz [
  <!ENTITY lol "lol">
  <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
  <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
  <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
]>
<lolz>&lol4;</lolz>

This "Billion Laughs" payload defines nested entities that expand exponentially—&lol4; becomes 10^9 "lol" strings when fully resolved. In lib/xml2json.js, an attacker exploiting this could:

  1. Submit malicious XML to any endpoint using odata-csdl
  2. Trigger the xml2json() function at line 36
  3. Cause the preParser or parser to consume all available memory
  4. Crash the Node.js process with an out-of-memory error

The real-world impact extends beyond denial of service. In environments where the SAX parser might resolve external entities (depending on Node.js version and sax configuration), this could also enable Server-Side Request Forgery (SSRF) or local file disclosure.

The Fix

The remediation is elegantly simple: add strictEntities: true to both parser configurations. This single flag instructs the sax parser to reject all entity declarations, preventing both exponential expansion and external entity resolution.

Before (vulnerable):

// lib/xml2json.js:58
const preParser = sax.parser(true, { xmlns: true });

// lib/xml2json.js:631
const parser = sax.parser(true, { xmlns: true });

After (fixed):

// lib/xml2json.js:58
const preParser = sax.parser(true, { xmlns: true, strictEntities: true });

// lib/xml2json.js:631
const parser = sax.parser(true, { xmlns: true, strictEntities: true });

This change preserves all legitimate functionality—the xmlns: true option continues to handle XML namespaces correctly, and valid XML documents without entity declarations parse identically. The only affected inputs are those containing <!ENTITY> declarations, which are now correctly rejected as potentially malicious.

The version bump in package.json (0.11.1 → 0.11.2) signals this security patch to downstream consumers, enabling automated vulnerability scanners to flag outdated dependencies.

Key Takeaways

  • The sax parser's default behavior permits entity expansion—secure configuration requires explicit opt-out with strictEntities: true
  • Both parsing phases in xml2json.js needed hardening—the preParser at line 58 and main parser at line 631 shared identical vulnerable patterns
  • Namespace awareness (xmlns: true) provides no XXE protection—these are orthogonal concerns that must both be configured
  • Data transformation libraries are high-value attack targets—odata-csdl processes structured data that often originates from external sources
  • Patch propagation matters—the 0.11.2 version bump enables dependency scanners to protect downstream consumers

How Orbis AppSec Detected This

Source: XML input passed to module.exports.xml2json() function in lib/xml2json.js:36

Sink: sax.parser(true, { xmlns: true }) calls at lines 58 and 631 without strictEntities restriction

Missing control: Absence of strictEntities: true option that would disable entity declaration processing

CWE: CWE-611: Improper Restriction of XML External Entity Reference

Fix: Added strictEntities: true to both SAX parser instantiations, completely blocking entity expansion while preserving namespace-aware parsing.

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

The odata-csdl XXE vulnerability demonstrates how a single missing configuration option in widely-used libraries can expose entire application stacks to resource exhaustion attacks. The sax package's default permissiveness toward entity expansion is a footgun that has bitten countless Node.js applications. By adding strictEntities: true to both parser instances, this fix eliminates the attack surface without sacrificing functionality. For developers maintaining XML-processing code, this incident underscores the importance of explicitly securing parser configurations—never assume defaults are safe when handling untrusted input.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #118

Related Articles

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

high

brace-expansion Stack Exhaustion: CVE-2026-102276 Patched

A critical stack exhaustion vulnerability in brace-expansion allows attackers to crash Node.js applications by supplying specially crafted brace patterns that trigger unbounded recursion. The fix upgrades the library across all maintained version lines to enforce depth limits on recursive expansion. This vulnerability affects any service that expands user-controlled brace patterns without input validation.

critical

BFF Proxy QR Code Endpoint Prototype Pollution via Unvalidated JSON

A critical prototype pollution vulnerability in a backend-for-frontend (BFF) proxy endpoint allowed attackers to inject malicious properties into the JavaScript Object prototype by crafting JSON requests with forbidden keys. This could compromise application behavior across all objects. The fix adds explicit key validation to reject payloads containing `__proto__`, `constructor`, or `prototype`.

high

smol-toml 1.7.0 DoS: Malformed TOML Documents Crash Parser

A denial-of-service vulnerability in smol-toml 1.7.0 allows attackers to crash the parser by supplying malformed TOML documents. The vulnerability affects any application that parses untrusted TOML input. The fix, available in smol-toml 1.7.1, hardens input validation and error recovery.