CWE-611: XML External Entity (XXE) Injection - JavaScript/Node.js
Overview
XML External Entity (XXE) injection in JavaScript/Node.js occurs when XML parsers process external entity references without proper security configuration. Node.js applications parsing XML from SOAP APIs, SVG uploads, RSS/Atom feeds, or configuration files are at risk when a parser or parser option loads DTDs, expands entities, or allows XML with entity declarations to flow into another component. XXE can enable file disclosure, SSRF, and denial-of-service attacks.
Primary Defence: Reject DOCTYPE and ENTITY declarations for untrusted XML, use a maintained parser with entity expansion disabled, and set library-specific options such as noent: false, dtdload: false, nonet: true, or processEntities: false where applicable. Avoid unmaintained native bindings for new code.
Common Vulnerable Patterns
libxmljs/libxmljs2 with Entity Expansion Enabled
const libxmljs = require('libxmljs2');
const express = require('express');
const app = express();
app.use(express.text({ type: 'application/xml' }));
app.post('/parse-xml', (req, res) => {
const xmlData = req.body;
try {
// VULNERABLE - Entity expansion and DTD loading are enabled
const xmlDoc = libxmljs.parseXml(xmlData, {
noent: true,
dtdload: true,
nonet: false
});
const title = xmlDoc.get('//title').text();
res.json({ title });
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// Attack payload:
// <?xml version="1.0"?>
// <!DOCTYPE foo [
// <!ENTITY xxe SYSTEM "file:///etc/passwd">
// ]>
// <data><title>&xxe;</title></data>
//
// Result: /etc/passwd contents leaked in response
Why this is vulnerable:
- External entities are resolved when entity substitution and DTD loading are enabled.
- Enables file disclosure, SSRF, and entity expansion DoS.
xml2js Without DOCTYPE Rejection
const xml2js = require('xml2js');
app.post('/upload-config', async (req, res) => {
const xmlConfig = req.body;
try {
// VULNERABLE - XML with DTD/entity declarations is accepted and may be
// logged, forwarded, or reparsed by a less restricted downstream component
const parser = new xml2js.Parser();
const result = await parser.parseStringPromise(xmlConfig);
res.json({ config: result });
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// Attack: Billion Laughs DoS
// <?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>
Why this is vulnerable:
- DOCTYPE is accepted without explicit rejection.
- The payload can become dangerous if later forwarded to a parser that expands entities.
- Rejecting DTDs at the boundary avoids parser-specific behavior differences.
JSDOM SVG Parsing Without XML Boundary Checks
const { JSDOM } = require('jsdom');
app.post('/parse-svg', (req, res) => {
const svgData = req.body;
// VULNERABLE - Untrusted SVG is parsed without rejecting DTD/entity declarations
const dom = new JSDOM(svgData, {
contentType: 'image/svg+xml'
});
const svgElement = dom.window.document.querySelector('svg');
const width = svgElement.getAttribute('width');
res.json({ width });
});
// Attack: XML with entity declarations reaches the SVG processing path
// <?xml version="1.0" standalone="no"?>
// <!DOCTYPE svg [
// <!ENTITY xxe SYSTEM "file:///etc/passwd">
// ]>
// <svg width="&xxe;" height="200"></svg>
Why this is vulnerable:
- DTD/entity declarations are accepted into a user-controlled XML workflow.
- SVG parsing often sits near sanitization, rendering, and storage code, so unsafe XML should be rejected before it reaches downstream processors.
- Resource loading and script execution are separate risks that must stay disabled for untrusted SVG.
fast-xml-parser with Entity Expansion
const { XMLParser } = require('fast-xml-parser');
app.post('/parse-feed', (req, res) => {
const feedXml = req.body;
const options = {
// VULNERABLE - processEntities enabled
processEntities: true,
allowBooleanAttributes: true
};
const parser = new XMLParser(options);
const result = parser.parse(feedXml);
res.json({ feed: result });
});
// Attack: Entity expansion / resource exhaustion
// <?xml version="1.0"?>
// <!DOCTYPE foo [
// <!ENTITY a "aaaaaaaaaa">
// <!ENTITY b "&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;">
// ]>
// <rss><channel><title>&b;</title></channel></rss>
Why this is vulnerable:
processEntities: trueexpands entity references.- Entity expansion can create denial-of-service risk; external entity behavior depends on parser/library support and should not be relied on as safe.
xmldom Parser Without DOCTYPE Rejection
const { DOMParser } = require('xmldom');
app.post('/parse-soap', (req, res) => {
const soapXml = req.body;
// VULNERABLE - No boundary check for DTD/entity declarations
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(soapXml, 'text/xml');
const bodyNode = xmlDoc.getElementsByTagName('Body')[0];
const data = bodyNode.textContent;
res.json({ data });
});
// Attack: Entity declaration accepted into SOAP processing workflow
// <?xml version="1.0"?>
// <!DOCTYPE foo [
// <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">
// ]>
// <soap:Envelope>
// <soap:Body>&xxe;</soap:Body>
// </soap:Envelope>
//
// Risk: payload may be logged, forwarded, transformed, or reparsed by a component
// that does resolve external entities.
Why this is vulnerable:
- The XML boundary accepts DTD/entity declarations.
- Downstream SOAP tooling or XML transforms may have different entity handling.
expat-based Parsers with External Entity Handlers
const expat = require('node-expat');
app.post('/stream-parse', (req, res) => {
const xmlStream = req.body;
// VULNERABLE - External entity handlers or DTD support are enabled elsewhere
const parser = new expat.Parser('UTF-8');
let result = '';
parser.on('text', (text) => {
result += text;
});
parser.on('error', (error) => {
res.status(400).json({ error: error.message });
});
parser.on('end', () => {
res.json({ result });
});
parser.write(xmlStream);
parser.end();
});
// Attack: Read AWS credentials file
// <?xml version="1.0"?>
// <!DOCTYPE foo [
// <!ENTITY creds SYSTEM "file:///home/ec2-user/.aws/credentials">
// ]>
// <data>&creds;</data>
Why this is vulnerable:
- Expat does not fetch external URLs by default, but applications can become vulnerable when they add external entity handlers or forward accepted DTDs to other XML processors.
SVG Upload Processing
const multer = require('multer');
const libxmljs = require('libxmljs2');
const upload = multer({ dest: 'uploads/' });
app.post('/upload-logo', upload.single('logo'), (req, res) => {
const fs = require('fs');
const svgContent = fs.readFileSync(req.file.path, 'utf8');
try {
// VULNERABLE - Parse user-uploaded SVG without rejecting DTDs/entities
const svgDoc = libxmljs.parseXml(svgContent, {
noent: true,
dtdload: true,
nonet: false
});
const viewBox = svgDoc.root().attr('viewBox');
res.json({
filename: req.file.filename,
viewBox: viewBox ? viewBox.value() : null
});
} catch (error) {
res.status(400).json({ error: 'Invalid SVG' });
}
});
// Attack: Upload malicious SVG
// <!DOCTYPE svg [
// <!ENTITY secret SYSTEM "file:///app/config/secrets.json">
// ]>
// <svg viewBox="&secret;"></svg>
Why this is vulnerable:
- User-controlled SVG is parsed with entity substitution and DTD loading enabled.
SOAP API Client
const axios = require('axios');
const { DOMParser } = require('xmldom');
app.post('/call-soap-api', async (req, res) => {
const userInput = req.body.searchTerm;
const soapEnvelope = `
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<search>
<term>${userInput}</term>
</search>
</soap:Body>
</soap:Envelope>
`;
// Make SOAP request
const response = await axios.post('http://internal-soap-api/service', soapEnvelope, {
headers: { 'Content-Type': 'text/xml' }
});
// VULNERABLE - Parse response without protection
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(response.data, 'text/xml');
const result = xmlDoc.getElementsByTagName('result')[0].textContent;
res.json({ result });
});
// Attack: Malicious SOAP server response
// <?xml version="1.0"?>
// <!DOCTYPE foo [
// <!ENTITY xxe SYSTEM "file:///etc/shadow">
// ]>
// <soap:Envelope>
// <soap:Body>
// <result>&xxe;</result>
// </soap:Body>
// </soap:Envelope>
Why this is vulnerable:
- External SOAP XML is parsed without entity protections.
Secure Patterns
libxmljs2 with Entity Expansion Disabled
const libxmljs = require('libxmljs2');
app.post('/parse-xml', (req, res) => {
const xmlData = req.body;
try {
rejectDangerousXml(xmlData);
// SECURE - Disable external entity resolution
const xmlDoc = libxmljs.parseXml(xmlData, {
noent: false, // Do NOT expand entities
dtdload: false, // Do NOT load external DTDs
nonet: true // Do NOT allow network access
});
const title = xmlDoc.get('//title').text();
res.json({ title });
} catch (error) {
res.status(400).json({ error: error.message });
}
});
Why this works:
- Rejects DOCTYPE/ENTITY at the boundary and disables entity expansion, external DTD loading, and network access.
- Prefer a maintained pure-JavaScript parser for new code; keep libxml-style native bindings only where the dependency is actively maintained in your project.
xml2js with DOCTYPE Rejection
const xml2js = require('xml2js');
function rejectDoctype(xmlString) {
// SECURE - Explicitly reject DOCTYPE declarations
if (xmlString.trim().match(/<!DOCTYPE/i)) {
throw new Error('DOCTYPE declarations not allowed');
}
return xmlString;
}
app.post('/upload-config', async (req, res) => {
try {
const xmlConfig = rejectDoctype(req.body);
const parser = new xml2js.Parser({
explicitArray: false,
ignoreAttrs: false,
// xml2js doesn't expand external entities by default,
// but validation is defense-in-depth
strict: true
});
const result = await parser.parseStringPromise(xmlConfig);
res.json({ config: result });
} catch (error) {
res.status(400).json({ error: error.message });
}
});
Why this works:
- Rejects DOCTYPE before parsing, blocking entity declarations.
- Adds defense-in-depth beyond parser defaults.
fast-xml-parser with Entities Disabled
const { XMLParser } = require('fast-xml-parser');
app.post('/parse-feed', (req, res) => {
const feedXml = req.body;
const options = {
// SECURE - Disable entity processing
processEntities: false,
allowBooleanAttributes: true,
ignoreAttributes: false,
// Additional security
parseTagValue: true,
parseAttributeValue: false,
trimValues: true
};
const parser = new XMLParser(options);
const result = parser.parse(feedXml);
res.json({ feed: result });
});
Why this works:
processEntities: falseprevents entity expansion.- Extra parser options reduce attribute-based bypasses.
JSDOM with DOCTYPE Rejection and External Resources Disabled
const { JSDOM } = require('jsdom');
app.post('/parse-svg', (req, res) => {
const svgData = req.body;
if (/<!DOCTYPE|<!ENTITY/i.test(svgData)) {
return res.status(400).json({ error: 'DOCTYPE and ENTITY declarations not allowed' });
}
// SECURE - Keep jsdom defaults for no subresource loading and no script execution
const dom = new JSDOM(svgData, {
contentType: 'image/svg+xml'
});
const svgElement = dom.window.document.querySelector('svg');
const width = svgElement.getAttribute('width');
res.json({ width });
});
Why this works:
- Rejects XML entity declarations before parsing.
- Leaves jsdom's default subresource loading and script execution disabled for untrusted SVG.
Custom SAX Parser with Entity Rejection
const sax = require('sax');
app.post('/parse-rss', (req, res) => {
const rssXml = req.body;
// SECURE - Use SAX parser with strict mode, reject entities
const parser = sax.parser(true, {
trim: true,
normalize: true,
lowercase: true
});
let title = '';
let hasDoctype = false;
parser.ondoctype = (doctype) => {
// SECURE - Reject documents with DOCTYPE
hasDoctype = true;
};
parser.ontext = (text) => {
title = text;
};
parser.onerror = (error) => {
res.status(400).json({ error: error.message });
};
parser.onend = () => {
if (hasDoctype) {
res.status(400).json({ error: 'DOCTYPE not allowed' });
} else {
res.json({ title });
}
};
parser.write(rssXml).close();
});
Why this works:
- sax does not expand external entities by default.
- DOCTYPE detection rejects dangerous constructs.
Input Validation with Schema
const Ajv = require('ajv');
const { XMLParser } = require('fast-xml-parser');
const ajv = new Ajv();
const xmlContentSchema = {
type: 'string',
maxLength: 50000, // Limit size
not: {
pattern: '<!ENTITY|<!DOCTYPE|SYSTEM|PUBLIC' // Reject entity declarations
}
};
const validateXmlContent = ajv.compile(xmlContentSchema);
app.post('/secure-parse', (req, res) => {
const xmlData = req.body;
// SECURE - Validate before parsing
if (!validateXmlContent(xmlData)) {
return res.status(400).json({
error: 'Invalid XML content',
details: validateXmlContent.errors
});
}
const parser = new XMLParser({
processEntities: false,
ignoreAttributes: false
});
const result = parser.parse(xmlData);
res.json({ data: result });
});
Why this works:
- Schema rejects DOCTYPE/ENTITY markers before parsing.
processEntities: falseadds a second safety layer.
SVG Upload with Sanitization
const multer = require('multer');
const { XMLParser } = require('fast-xml-parser');
const DOMPurify = require('isomorphic-dompurify');
const upload = multer({
dest: 'uploads/',
limits: { fileSize: 1024 * 1024 } // 1MB limit
});
app.post('/upload-logo', upload.single('logo'), (req, res) => {
const fs = require('fs');
let svgContent = fs.readFileSync(req.file.path, 'utf8');
// SECURE - Reject DOCTYPE
if (/<!DOCTYPE|<!ENTITY/i.test(svgContent)) {
fs.unlinkSync(req.file.path);
return res.status(400).json({ error: 'Invalid SVG: DOCTYPE not allowed' });
}
// SECURE - Sanitize SVG
svgContent = DOMPurify.sanitize(svgContent, {
USE_PROFILES: { svg: true, svgFilters: true }
});
// Save sanitized version
fs.writeFileSync(req.file.path, svgContent);
res.json({
filename: req.file.filename,
status: 'uploaded and sanitized'
});
});
Why this works:
- Size limits and DOCTYPE rejection stop common XXE payloads.
- Sanitization strips unsafe elements and references.
Prefer JSON Over XML
// SECURE - Use JSON instead of XML when possible
app.post('/api/data', express.json(), (req, res) => {
const data = req.body;
// Process JSON data (no XXE risk)
res.json({
received: data,
processedAt: new Date().toISOString()
});
});
// If XML is required for legacy reasons:
app.post('/legacy-xml', express.text({ type: 'application/xml' }), (req, res) => {
// Convert XML to JSON safely
const { XMLParser } = require('fast-xml-parser');
const xmlData = req.body;
if (xmlData.includes('<!DOCTYPE')) {
return res.status(400).json({ error: 'DOCTYPE not allowed' });
}
const parser = new XMLParser({ processEntities: false });
const jsonData = parser.parse(xmlData);
res.json(jsonData);
});
Why this works:
- JSON avoids XML entity risks entirely.
- Legacy XML is gated by DOCTYPE rejection and safe parsing.
Key Security Functions
DOCTYPE Detection and Rejection
function hasDoctype(xmlString) {
return /<!DOCTYPE/i.test(xmlString);
}
function hasEntityDeclaration(xmlString) {
return /<!ENTITY/i.test(xmlString);
}
function rejectDangerousXml(xmlString) {
if (hasDoctype(xmlString)) {
throw new Error('DOCTYPE declarations are not allowed');
}
if (hasEntityDeclaration(xmlString)) {
throw new Error('ENTITY declarations are not allowed');
}
return xmlString;
}
// Usage:
app.post('/parse', (req, res) => {
try {
const safeXml = rejectDangerousXml(req.body);
// Parse safeXml...
} catch (error) {
res.status(400).json({ error: error.message });
}
});
Safe Parser Configuration Helper
// For libxmljs2/libxml-style parsers
function createSafeLibxmljsOptions() {
return {
noent: false, // Don't expand entities
dtdload: false, // Don't load DTDs
dtdvalid: false, // Don't validate against DTD
nonet: true, // Disable network access
nocdata: false, // Keep CDATA
noblanks: false // Keep whitespace
};
}
// For fast-xml-parser
function createSafeFastXmlOptions() {
return {
processEntities: false, // Critical: disable entity processing
allowBooleanAttributes: true,
ignoreAttributes: false,
parseTagValue: true,
parseAttributeValue: false,
trimValues: true,
cdataPropName: false, // Don't expose CDATA in a separate property
commentPropName: false // Don't include comments
};
}
File Size Limits
const express = require('express');
app.use(express.text({
type: 'application/xml',
limit: '1mb' // Limit XML size to prevent DoS
}));
// Or with multer for file uploads:
const multer = require('multer');
const upload = multer({
limits: {
fileSize: 1024 * 1024, // 1MB
files: 1
},
fileFilter: (req, file, cb) => {
// Only accept specific MIME types
if (file.mimetype === 'image/svg+xml' || file.mimetype === 'application/xml') {
cb(null, true);
} else {
cb(new Error('Invalid file type'));
}
}
});
Framework-Specific Guidance
Express.js with Multiple XML Libraries
const express = require('express');
const { XMLParser } = require('fast-xml-parser');
const libxmljs = require('libxmljs2');
const app = express();
app.use(express.text({ type: 'application/xml', limit: '1mb' }));
// Middleware: Reject DTD/entity declarations
app.use('/api/*', (req, res, next) => {
if (req.is('application/xml') && /<!DOCTYPE|<!ENTITY/i.test(req.body)) {
return res.status(400).json({ error: 'DOCTYPE and ENTITY declarations not allowed' });
}
next();
});
// Route 1: fast-xml-parser
app.post('/api/parse-simple', (req, res) => {
const parser = new XMLParser({ processEntities: false });
const result = parser.parse(req.body);
res.json(result);
});
// Route 2: libxmljs2 for XPath queries
app.post('/api/parse-xpath', (req, res) => {
const doc = libxmljs.parseXml(req.body, {
noent: false,
dtdload: false,
nonet: true
});
const titles = doc.find('//title').map(node => node.text());
res.json({ titles });
});
NestJS Service
import { Injectable, BadRequestException } from '@nestjs/common';
import { XMLParser } from 'fast-xml-parser';
@Injectable()
export class XmlParserService {
private readonly parser: XMLParser;
constructor() {
this.parser = new XMLParser({
processEntities: false, // Disable entity processing
ignoreAttributes: false,
parseAttributeValue: false
});
}
parseXml(xmlString: string): any {
// Reject DOCTYPE declarations
if (/<!DOCTYPE/i.test(xmlString)) {
throw new BadRequestException('DOCTYPE declarations not allowed');
}
// Reject entity declarations
if (/<!ENTITY/i.test(xmlString)) {
throw new BadRequestException('ENTITY declarations not allowed');
}
try {
return this.parser.parse(xmlString);
} catch (error) {
throw new BadRequestException('Invalid XML');
}
}
}
Fastify Plugin
const fastify = require('fastify')();
const { XMLParser } = require('fast-xml-parser');
fastify.addContentTypeParser('application/xml', { parseAs: 'string' }, (req, body, done) => {
// Reject DOCTYPE
if (/<!DOCTYPE/i.test(body)) {
done(new Error('DOCTYPE not allowed'), undefined);
return;
}
const parser = new XMLParser({
processEntities: false
});
try {
const result = parser.parse(body);
done(null, result);
} catch (error) {
done(error, undefined);
}
});
fastify.post('/parse', async (request, reply) => {
// request.body already parsed securely
return { data: request.body };
});
Typical XXE Findings
-
"XML parser configured to resolve external entities"
- Location:
libxmljs.parseXml(xmlData, { noent: true, dtdload: true }) - Fix: Reject
DOCTYPE/ENTITYbefore parsing and add options such as{ noent: false, dtdload: false, nonet: true }
- Location:
-
"XXE vulnerability via DOCTYPE processing"
- Location: XML parsing without DOCTYPE rejection
- Fix: Reject XML containing
<!DOCTYPEbefore parsing
-
"Potential Billion Laughs DoS via entity expansion"
- Location: Parser allows entity expansion
- Fix: Disable entity processing:
processEntities: false
-
"User-controlled XML parsed without validation"
- Location: Direct parsing of request body as XML
- Fix: Add size limits, DOCTYPE rejection, secure parser configuration
-
"SVG upload without XXE protection"
- Location: Parsing uploaded SVG files
- Fix: Sanitize SVG, reject DOCTYPE, use DOMPurify
-
"External DTD loading enabled"
- Location: Parser configuration
- Fix: Set
dtdload: falseor equivalent for your library
Remediation Steps
- Locate every XML parsing path, including request bodies, uploaded SVG files, SOAP clients, RSS/Atom feeds, SAML-like integrations, background jobs, and downstream reparsing.
- Identify the parser and configuration used on each path, including whether DTD loading, entity expansion, external entity handlers, or network access can occur.
- Reject untrusted XML containing
DOCTYPEorENTITYdeclarations before parsing, especially where parser defaults differ or downstream components may reparse the data. - Configure the parser to disable entity processing and external resource loading, such as
processEntities: false,noent: false,dtdload: false, andnonet: truewhere supported. - Add input size limits, file upload limits, SVG sanitization, and schema validation where those controls fit the data format.
- Prefer JSON or another non-XML format when XML is not required by an external standard or integration.
Testing
- Test normal XML documents, SOAP envelopes, SVG uploads, and feed payloads that the application is expected to accept.
- Test file disclosure XXE payloads using local file entities and confirm the entity is rejected before parsing or never expanded.
- Test SSRF-style payloads that reference internal HTTP metadata endpoints and confirm no outbound request is attempted.
- Test Billion Laughs or nested entity expansion payloads with strict size and timeout limits in place.
- Test mixed-case
DOCTYPEandENTITYdeclarations, XML sent through uploads, queued jobs, and third-party API responses. - Retest with SAST, dependency checks, and DAST tools for parser options, unmaintained XML packages, and XML routes that lack boundary rejection.
Common Pitfalls
- Trusting parser defaults without rejecting DTD/entity declarations at the application boundary.
- Disabling external entities in one parser while forwarding the same XML to another parser, transformer, logger, or SOAP library.
- Treating SVG sanitization as XXE protection when the XML parser has already processed the document.
- Allowing large XML payloads without size limits, even when external entity loading is disabled.
- Using unmaintained native XML bindings for new code without reviewing parser behavior and dependency risk.
- Assuming
nonetprevents local file disclosure; it only addresses network access for parsers that support it.
Defense in Depth
Layer 1: Parser Configuration (Primary)
- Disable external entity resolution
- Disable DTD loading
- Disable parameter entities
- Use maintained libraries with reviewed XML security defaults
Layer 2: Input Validation (Secondary)
- Reject DOCTYPE declarations
- Reject ENTITY declarations
- Enforce size limits (prevent DoS)
- Validate against XML schema (XSD)
Layer 3: Alternative Formats (Tertiary)
- Prefer JSON over XML when possible
- Use Protocol Buffers for structured data
- Only use XML when required by standards (SOAP, SAML)
Layer 4: Monitoring and Detection (Infrastructure)
- Monitor outbound connections from XML parsers
- Alert on file access from XML processing
- Log and review all XML parsing errors
Dependencies and Installation
- Prefer maintained JavaScript parsers such as
fast-xml-parserfor new XML handling where they meet functional requirements. - If using
libxmljs2or another native/libxml-style binding, verify active maintenance and explicitly set safe parser options. - Use
isomorphic-dompurifyor another maintained sanitizer for SVG/HTML sanitization after rejecting dangerous XML declarations. - Keep XML, upload, SOAP, and DOM libraries current because parser behavior and security defaults vary by version.
- Remove unused XML parsers from the dependency graph to reduce the chance of a later unsafe reparsing path.
Security Checklist
- Disable external entity resolution in all XML parsers (
noent: false,processEntities: false) - Disable DTD loading (
dtdload: false) - Reject XML containing DOCTYPE declarations before parsing
- Set size limits for XML input (prevent DoS)
- Use actively maintained libraries; avoid unmaintained native XML bindings for new code
- Never use deprecated XML libraries (xml-parser, node-xml)
- Sanitize SVG uploads with DOMPurify
- Prefer JSON over XML when possible
- Test with XXE payloads: file disclosure, SSRF, Billion Laughs
- Monitor file access and network connections from XML parsing
- Review third-party library configurations for XXE protection
- Validate XML against strict schema (XSD) when possible
Additional Resources
- DOMPurify for SVG
- fast-xml-parser Documentation
- libxmljs2 - legacy/native-binding reference; verify maintenance before using
- OWASP XXE Prevention Cheat Sheet
- PayloadsAllTheThings XXE