If you have landed here you probably typed something close to libxmljs2 dtdload default into a search box, because a security review flagged your XML parser and you need to know whether the box was already checked.
Short answer: dtdload defaults to false. So does noent. Every parser option in libxmljs2 is opt-in, and a bare parseXml(xml) call is not the vulnerable configuration.
The longer answer matters more, because the option most guides tell you to worry about is not the one that opens the hole.
TL;DR
dtdload and noent are both off by default in libxmljs2. Turning on noent alone is enough to read local files through an external entity; turning on dtdload alone is not. Adding nonet: true does not close it, because nonet blocks the network and the payload uses file://. The real risk in most projects is not the flags at all, it's that libxmljs2 stopped shipping releases in June 2025 and vendors libxml2 2.9.9.
Where the default actually comes from
libxmljs2 builds a libxml2 flag bitmask from the options object you hand parseXml. The whole behaviour lives in one helper in src/xml_document.cc:
int getParserOption(Local<Object> props, const char *key, int value,
bool defaultValue = true) {
Nan::HandleScope scope;
Local<Value> prop =
Nan::Get(props, Nan::New<String>(key).ToLocalChecked()).ToLocalChecked();
return !prop->IsUndefined() && Nan::To<bool>(prop).ToChecked() == defaultValue
? value
: 0;
}
Read the return expression. The flag is contributed only when the property is defined and its boolean value matches. Leave dtdload out and prop->IsUndefined() is true, so the function returns 0 and XML_PARSE_DTDLOAD never enters the mask.
That is the entire answer to "what is the dtdload default". There is no separate defaults object anywhere in the package, and no config file that flips it. Undefined means off.
The same helper handles noent, dtdattr, dtdvalid, recover, nonet, huge and the rest. Passing dtdload: false and omitting dtdload produce identical bitmasks, so { noent: false, dtdload: false } is a comment to your future self rather than a hardening step.
What we tested, and what leaked
Knowing the default is off is useful. Knowing which flag actually causes the damage is more useful, and most advice on this gets it backwards by focusing on dtdload.
We installed libxmljs2 0.37.0, wrote a secret to a local file, and parsed the classic external entity payload once per option combination:
<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file:///tmp/xxe-secret.txt">
]>
<root><data>&xxe;</data></root>
Results against libxmljs2 0.37.0 on Node 22, reading //data:
Options passed to parseXml | Result |
|---|---|
{} | empty string |
{ noent: false, dtdload: false } | empty string |
{ dtdload: true } | empty string |
{ noent: true } | file contents returned |
{ noent: true, dtdload: true } | file contents returned |
{ noent: true, dtdload: true, nonet: true } | file contents returned |
Three things fall out of that table.
noent: true on its own is the vulnerability. You do not need dtdload. If your codebase sets noent anywhere near untrusted XML, that line is the finding, and no amount of dtdload: false next to it helps.
dtdload: true on its own did not leak. It loads the external subset; it doesn't substitute the entity into the text node. Useful to know when you're triaging a scanner alert that flagged dtdload and nothing else.
nonet: true does not save you. It maps to XML_PARSE_NONET, which forbids network access. Our payload never touched the network. A file:///etc/passwd or file:///proc/self/environ entity is a local read, and nonet has no opinion about it. We have seen this exact combination pass an internal review because someone grepped for nonet and found it present.
Why people set noent in the first place
Nobody enables it to be reckless. They enable it because their XML has legitimate internal entities and the parsed output is full of unresolved &thing; references, and noent: true is the first Stack Overflow answer that makes the symptom go away.
If that is your situation, the fix is not a flag. It's rejecting the DOCTYPE before parsing:
const libxmljs = require('libxmljs2');
function parseUntrusted(xml) {
// Cheap pre-filter. An external entity needs a DOCTYPE to declare it.
if (/<!DOCTYPE/i.test(xml)) {
throw new Error('DTD not allowed');
}
return libxmljs.parseXml(xml, {
noent: false,
nonet: true,
dtdload: false,
dtdvalid: false,
});
}
The regex is a guard, not the security boundary. The parser defaults are doing the real work; the check just gives you a clear error instead of a silently empty node, which is what your support inbox actually needs.
If your XML genuinely requires entity expansion, resolve entities in your own code after parsing, against an allowlist you control. Never hand that decision to the parser on input you did not write.
The bigger problem is the package, not the flag
While confirming the defaults we pulled the published tarball, and the parser options turned out to be the less interesting finding.
The repository README opens with "NO LONGER MAINTAINED." The last release on npm, 0.37.0, went out on 1 June 2025. And the package vendors its own copy of libxml2 rather than linking the system one:
#define LIBXML_DOTTED_VERSION "2.9.9"
#define LIBXML_VERSION 20909
libxml2 2.9.9 is a 2019 release. It predates the fix for CVE-2021-3518, a use-after-free in xmlXIncludeDoProcess() reachable when processing crafted files, which landed in 2.9.11. And binding.gyp in the same tarball compiles with LIBXML_XINCLUDE_ENABLED, so XInclude is present in the build rather than compiled out.
Your package.json may not mention libxmljs2 at all. It is a transitive dependency of several XML and SAML packages, and because it vendors libxml2, upgrading your operating system's libxml2 changes nothing. Run npm ls libxmljs2 to see whether it's in there and what pulled it in.
Nothing here is a live exploit against your app. It's a maintenance status: an archived binding, wrapping a six-year-old C library, that no longer gets patches when the C library does. That's the thing worth putting on a roadmap, not another dtdload: false.
The check to run today
npm ls libxmljs2 in your project root. If nothing comes back, you're done.
If it's present, grep -rn "noent" --include="*.js" --include="*.ts" src/ and read every hit. noent: true next to user-supplied XML is the finding.
Check the same for .xml handling in serverless functions and webhook receivers, which is where XML parsing usually hides in a vibe-coded app. SAML callbacks and payment provider webhooks are the two common sources.
Decide whether you still need XML at all. Many projects carry this dependency for one legacy endpoint that could take JSON instead.
For the mechanism behind all of this, and the equivalent settings in Python, Java and PHP parsers, read our XXE explainer.
What is the default value of dtdload in libxmljs2?
False. In src/xml_document.cc the XML_PARSE_DTDLOAD flag is only added when the dtdload property is defined and strictly true, so calling parseXml with no options leaves it off. The same helper governs noent, dtdvalid, dtdattr and the rest.
Is noent false by default in libxmljs2?
Yes. Every parser option in libxmljs2 is opt-in. An undefined property contributes zero to the flag bitmask, so parseXml(xml) runs with no XML_PARSE_NOENT and no XML_PARSE_DTDLOAD.
Does dtdload true on its own create an XXE vulnerability?
Not in our test. Against libxmljs2 0.37.0, parsing a document with a file:// external entity under dtdload: true returned an empty node. The same document under noent: true returned the file contents. dtdload loads the external subset; noent is what substitutes the entity into the text.
Does nonet true protect against XXE in libxmljs2?
Not against local file disclosure. We parsed a file:// entity with noent: true, dtdload: true and nonet: true, and it still returned the file contents. nonet forbids network access, which does nothing about a file:// URI.
Is libxmljs2 still maintained?
No. The repository README states it is no longer maintained, and the last npm release, 0.37.0, was published on 1 June 2025. It vendors libxml2 2.9.9, a 2019 release that predates the fix for CVE-2021-3518.
Not sure what your app parses?
Scan your deployed site for XML handling, exposed config, and dependencies nobody maintains.