Flag XML parsers that can process external entities — before an attacker feeds them one.
An XML parser left with its defaults will happily read a document like this:
<?xml version="1.0"?>
<!DOCTYPE r [ <!ENTITY x SYSTEM "file:///etc/passwd"> ]>
<r>&x;</r>…resolve that external entity, and hand the contents of /etc/passwd back in the
response. Point the entity at an internal URL instead and it becomes SSRF; point
it at a nested-entity bomb and it becomes a denial of service. This is
XXE — XML External Entity injection (CWE-611),
and the fix is always the same: configure the parser to refuse DOCTYPEs and
external entities. The trouble is that the insecure configuration is usually
the default one, so the vulnerability is a line of code that looks perfectly
ordinary.
xmlfend is a zero-config static gate that scans your code for XML parsers
instantiated or used in an unsafe way, and flags them — grounded in the
OWASP XXE Prevention Cheat Sheet.
$ xmlfend .
● 2 insecure XML parser(s):
app/parse.py:19 lxml XML parser created without disabling external-entity resolution.
↳ lxml resolves external entities unless you pass resolve_entities=False ...
[XX001 Python/lxml]
app/parse.py:24 Standard-library XML parser used on input without defusedxml.
↳ xml.etree / xml.dom.minidom / xml.sax do not defend against external entities ...
[XX001 Python/stdlib]
2 blockers · 0 warnings
Exit code 1 when it finds an insecure parser, so it drops straight into
pre-commit or CI.
For each source file, xmlfend looks for a known XML parser used in a risky construct (the trigger), then checks whether the file also applies that parser's XXE hardening (the safe marker). If the risky construct is present and the hardening is not, it reports the line.
The safe-marker short-circuit is what keeps false-positives low: a file that
imports defusedxml, sets resolve_entities=False, calls
libxml_disable_entity_loader(true), sets disallow-doctype-decl, uses
DtdProcessing.Prohibit, or nulls the XmlResolver is treated as safe. No code
is executed and no network calls are made — it's a single static binary.
Coverage: Python (lxml, xml.etree/minidom/sax), Java/Kotlin/Scala
(JAXP factories), PHP (libxml, simplexml, DOMDocument), .NET
(XmlReaderSettings, XmlDocument), Node.js (libxmljs), Ruby (Nokogiri).
go install github.com/jay-tank/xmlfend@latestOr build from source: go build -o xmlfend .
xmlfend # scan the current directory
xmlfend ./src # scan a path
xmlfend --strict # treat warnings (e.g. an inline DTD) as failures too
xmlfend --json # machine-readable output- run: go run github.com/jay-tank/xmlfend@latest ./ --strictExit codes: 0 clean · 1 an insecure parser (or any finding under --strict)
· 2 usage error.
| Rule | Severity | What |
|---|---|---|
| XX001 | blocker | A known XML parser is instantiated / used in a way that can process external entities or a DTD, with no hardening present |
| XX002 | warning | An explicit DTD / entity declaration (<!DOCTYPE ... [, <!ENTITY ...>) appears in source |
Every finding cites the OWASP XXE Prevention Cheat Sheet and CWE-611 in its hint.
xmlfend is a heuristic gate that reads each file on its own. If you harden the
parser in a shared factory (a different file), add a xmlfend:ignore comment on
the flagged line or the line directly above it, or list a path substring in a
.xmlfendignore file (one per line, # for comments).
xmlfend answers one question well — "is this XML parser configured to keep external-entity / DTD processing on?" — across languages, as a fast static gate. It is a line/file-based heuristic: it does not build a full data-flow graph, so it favors the honest, low-noise call (a file that shows the hardening is treated as safe even if the hardening technically applies to a different parser in the file). That trade catches the overwhelmingly common case — a parser left on its insecure defaults — without drowning you in noise.
MIT © Jay Tank