If you landed here searching for what dtdload defaults to in libxmljs2, the answer is false. noent is false too, and so is every other parser option the library exposes, because libxmljs2 starts its libxml2 flag set at zero and only adds a flag when you pass that option as true.
That is good news and it is also not the end of the story. Node's XML stack has three traps that a "just set the flags" answer walks straight past: one popular option name means the opposite of what it reads like, the flag libxml2 now recommends for this is not reachable from libxmljs2 at all, and the nonet: true line in most hardening guides stopped doing anything.
TL;DR
dtdload and noent both default to false in libxmljs2, so a bare parseXml(input) is not the classic XXE hole. The real risk is a wrapper or a teammate passing noent: true to get entity text expanded, which turns external entity loading back on. nonet: true no longer helps on libxml2 2.15+, and libxmljs2 does not expose libxml2's newer XML_PARSE_NO_XXE flag.
Parser Defaults at a Glance
| Library | Unsafe by default? | The setting that matters | Safe value |
|---|---|---|---|
libxmljs2 (Node) | No | noent | omit it, or noent: false |
fast-xml-parser (Node) | No | none, no DTD support at all | nothing to set |
lxml (Python) | Yes, with a DOCTYPE present | resolve_entities | resolve_entities=False |
xml.etree.ElementTree (Python) | No | none for XXE, still entity-expansion prone | use defusedxml |
DocumentBuilderFactory (Java) | Yes | disallow-doctype-decl | true |
The Node row is the one people get backwards, so start there.
What dtdload and noent Actually Do
libxmljs2 maps its option names onto libxml2's XML_PARSE_* flags in one function, getParserOptions() in src/xml_document.cc. It initializes the flag set to 0 and then ORs in a flag only when the matching property is present and true. No property, no flag. That single line of logic is why the answer to "what is the libxmljs2 dtdload default" is false, and it holds for all 33 option names the file reads.
Several vulnerability databases claim noent defaults to true in libxmljs. It does not, and it never took a different code path. If you have seen that claim, it is worth rechecking anything else you took from the same page.
noent is the worst-named flag in XML parsing. It reads like "no entities". libxml2's own documentation opens the description with "Despite the confusing name, this option enables substitution of entities." Setting noent: true turns entity handling on, including loading external ones.
Which is exactly how the bug usually arrives. Someone parses a document, finds &customerName; sitting in the tree as an unexpanded entity reference node instead of the text they wanted, searches for a fix, and adds noent: true. The document parses correctly. The file-read primitive arrives with it.
const libxmljs = require('libxmljs2');
// Safe: no flags set, no external entity loading
const doc = libxmljs.parseXml(xmlString);
// Unsafe: entity substitution on, external entities now load
const doc = libxmljs.parseXml(xmlString, { noent: true });
If you need entity text expanded from documents you don't control, you need a different parser, not a different flag.
Two Pieces of Hardening Advice That Are Now Stale
nonet: true is no longer a network kill switch. Nearly every XXE guide tells you to pass it. libxml2's current docs are blunt about what it does now: after the last built-in network client was removed in 2.15, the option "has no effect except for being passed on to custom resource loaders." It costs nothing to keep, but do not count it as the thing stopping an SSRF.
libxml2's recommended flag is unreachable from libxmljs2. libxml2 added XML_PARSE_NO_XXE specifically to disable external entity loading, and its docs now point untrusted-input handlers at it. libxmljs2's getParserOptions() reads 33 option names and no_xxe is not one of them, so there is no way to set it through the JavaScript API. On this library your ceiling is "don't turn noent on."
If XML is a core part of your product and the input is untrusted, that ceiling is the argument for moving to fast-xml-parser. It has no DTD support at all, which means there is no flag to get wrong and no wrapper that can quietly re-enable one.
What Is XXE?
XML External Entity (XXE) is a vulnerability in applications that parse XML input. The XML spec allows "entities" that reference external resources. If the parser follows those references, an attacker controls what gets read.
The four attack classes:
- File read: Pull
/etc/passwd,.env, or any readable path - SSRF: Make server-side HTTP requests to internal IPs, cloud metadata endpoints, or internal APIs
- Denial of service: Recursive entity expansion ("billion laughs") that consumes all memory
- Port scan: Probe internal network services by watching which requests succeed or time out
How the Attack Works
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<userInfo>
<name>&xxe;</name>
</userInfo>
A parser with entity substitution enabled replaces &xxe; with the contents of /etc/passwd before your application code sees the data. The name field in your parsed document now holds the server's user list, and whatever your app does with that field next (log it, echo it back, store it) decides how far the leak travels.
Upload endpoints are the usual delivery route, because an XML, SVG, or DOCX upload reaches a parser without anyone thinking of it as parsing untrusted XML.
The SSRF variant swaps file:// for http://:
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">
]>
<userInfo><name>&xxe;</name></userInfo>
Auditing the Rest of Your Stack
libxmljs2 as a transitive dependency
The defaults protect your own call sites. They don't protect the ones you didn't write. If libxmljs2 arrived through another package, that package chose the options, and a wrapper that wants clean text output has every incentive to pass noent: true.
# Your code
grep -rn "parseXml\|parseXmlString" --include="*.js" --include="*.ts" .
# Your dependencies
grep -rn "noent" node_modules --include="*.js" | grep -v "noent: *false"
The second command is the one that finds surprises. Anything it prints is a package that turns entity substitution on for you.
Writing the flags out explicitly is still worth doing, not because the defaults are wrong but because it documents the intent for whoever edits the file next:
const libxmljs = require('libxmljs2');
const doc = libxmljs.parseXml(xmlString, {
noent: false, // keep entity substitution off (this is already the default)
dtdload: false, // don't load external DTDs
dtdvalid: false, // don't validate against DTD
});
fast-xml-parser avoids the question entirely:
const { XMLParser } = require('fast-xml-parser');
const parser = new XMLParser({
// No DTD or external entity support. XXE-safe out of the box.
});
const result = parser.parse(xmlString);
lxml (Python)
Python's lxml is the other common source of XXE bugs. Its etree.fromstring and etree.parse functions resolve external entities by default when the document includes a DOCTYPE.
from lxml import etree
# UNSAFE: resolves external entities
tree = etree.fromstring(xml_bytes)
import defusedxml.lxml as safe_lxml
# defusedxml patches lxml to block DTD loading, external entities, and entity expansion
tree = safe_lxml.fromstring(xml_bytes)
You can also configure lxml's parser directly:
from lxml import etree
parser = etree.XMLParser(
resolve_entities=False,
no_network=True,
load_dtd=False,
)
tree = etree.fromstring(xml_bytes, parser)
Java (DocumentBuilderFactory)
Java's default XML parser is also unsafe. This is OWASP's recommended hardening:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
Is Your App at Risk?
Most apps built with Cursor, Bolt, Lovable, or Replit use JSON APIs and are not vulnerable. XXE only matters if your server parses XML.
You are at risk if your app:
- Accepts XML file uploads (exports, imports, configuration)
- Integrates with SOAP APIs (older enterprise services)
- Parses SVG images server-side (thumbnailing, conversion)
- Processes Office documents (DOCX, XLSX, PPTX are ZIP+XML)
- Uses XML-based configuration files
If you use a PDF generation library that internally parses HTML as XML, or an image processing library that handles SVG, check the library's XXE posture even if you never write XML yourself.
How to Find XXE in Your App
Three places to check:
1. Grep for XML parser imports:
# Node.js
grep -r "require('libxmljs2')\|require('xml2js')\|require('sax')" .
# Python
grep -r "from lxml\|import xml.etree\|import libxml2" .
2. Look for multipart/form-data endpoints that accept XML or SVG content types.
3. Check any library that processes uploaded documents (DOCX parsers, spreadsheet parsers, diagram tools). Search the library's GitHub for "XXE" or "external entity" in open issues.
The Fastest Fix
If you only parse XML in one place and don't need DTD support (most apps don't), the fastest fix is to reject any document that contains a DOCTYPE declaration entirely:
function safeParseXml(xmlString) {
if (xmlString.includes('<!DOCTYPE') || xmlString.includes('<!ENTITY')) {
throw new Error('XML DOCTYPE/ENTITY declarations are not allowed');
}
return libxmljs.parseXml(xmlString, { noent: false, dtdload: false });
}
This is a belt-and-suspenders approach: reject suspicious input before the parser even runs.
What is the default value of dtdload in libxmljs2?
false. libxmljs2's getParserOptions() builds its libxml2 flag set starting from 0 and only ORs in a flag when the matching property is present and set to true. Pass no options and you get no flags, so dtdload is off. The same holds for all 33 option names the function reads.
Does noent default to true in libxmljs2?
No. Several vulnerability databases say it does, and the claim keeps getting copied forward. noent goes through the same one-line mechanism as every other option, so it is false unless you pass noent: true yourself. There is no alternate binding path where it flips.
What is an XXE attack?
XXE (XML External Entity) is an attack where a malicious XML payload tricks the parser into reading local files, making internal HTTP requests, or crashing the server via recursive entities. It targets the XML spec's "external entity" feature, which most apps have no reason to use.
Does setting nonet: true protect me from XXE?
Not the way it used to. libxml2 removed its last built-in network client in 2.15, and its documentation now says XML_PARSE_NONET "has no effect except for being passed on to custom resource loaders." Keep it if you like, but the thing actually preventing an outbound fetch is leaving noent and dtdload off, not this flag.
Can JSON APIs have XXE vulnerabilities?
No. XXE is specific to XML parsing. JSON parsers don't support external entities. If your API only accepts JSON, you're not vulnerable to XXE.
Check Your XML Handling
Our scanner tests for XXE vulnerabilities in your file upload endpoints and flags libraries with unsafe defaults.