@@ -480,6 +480,49 @@ Document::fromXmlFile(
480480);
481481```
482482
483+ #### disallow_doctype
484+
485+ Rejects any document that carries a ` <!DOCTYPE ...> ` declaration by throwing a ` VeeWee\Xml\Exception\DoctypeNotAllowedException ` .
486+ It works on the parsed document, so it applies uniformly to every loader — string, file and node alike.
487+
488+ ``` php
489+ use VeeWee\Xml\Dom\Document;
490+ use function VeeWee\Xml\Dom\Configurator\disallow_doctype;
491+
492+ Document::fromXmlString(
493+ $untrustedXml,
494+ disallow_doctype()
495+ );
496+ ```
497+
498+ ##### What this protects you from — and what it does not
499+
500+ It is important to be honest about what this guard buys you, because most of the heavy lifting against
501+ [ XML eXternal Entity (XXE)] ( https://owasp.org/www-community/vulnerabilities/XML_External_Entity_(XXE)_Processing )
502+ attacks is already done by libxml itself.
503+
504+ With the ** default options** that this library passes to ` Dom\XMLDocument ` (i.e. you do * not* add libxml flags yourself), libxml already:
505+
506+ * does ** not** read external file entities (` <!ENTITY x SYSTEM "file:///etc/passwd"> ` is never resolved);
507+ * does ** not** fetch external DTDs over the network (` <!DOCTYPE r SYSTEM "http://..."> ` triggers no request);
508+ * ** aborts** entity-amplification ("billion laughs") attacks before expanding them.
509+
510+ So on the default path you are already protected against the active XXE vectors, with or without this configurator.
511+ The only thing libxml still expands are small, non-amplifying * internal* entities — which is not an attack when the
512+ attacker already controls the whole document.
513+
514+ What ` disallow_doctype() ` therefore adds is ** policy and defence-in-depth** , not a fix for a libxml hole:
515+
516+ * it enforces a clear, uniform "no DOCTYPE allowed" rule across all loaders, with a typed exception — useful when your
517+ context forbids a DOCTYPE outright (e.g. SOAP / WS-Security / SAML);
518+ * it keeps you safe even if libxml's defaults ever change across versions, builds or ` php.ini ` settings.
519+
520+ ** What it does * not* protect against:** because it runs * after* parsing, it cannot undo anything that happened during
521+ parsing. If you knowingly (or unknowingly) hand the loader unsafe options such as ` LIBXML_NOENT ` or ` LIBXML_DTDLOAD ` ,
522+ libxml * will* read the file / fetch the DTD while parsing — before this configurator ever runs. The rejection then comes
523+ too late. The takeaway is simple: ** do not enable ` LIBXML_NOENT ` / ` LIBXML_DTDLOAD ` for untrusted input.** Keeping the
524+ default options is what actually keeps you safe; this configurator is the policy layer on top.
525+
483526#### document_uri
484527
485528Allows you to keep track of the document uri, even if you are using an in-memory string.
0 commit comments