Update: Java section of the XML External Entity Prevention Cheat Sheet

Author: ppkarwaszCreated Sep 4, 2026Updated Sep 7, 2026
LabelsACK_OBTAINEDUPDATE_CS

The XXE Prevention Cheat Sheet has been a pillar for Java developers, and its Java section is still the page people land on when they need to harden a parser. But it has aged: the advice is framed around Java 6 and 7, and while there have been typo fixes and small additions since, the structure and the examples have not been revisited. Some implementations it documents have died; others that matter today are not mentioned at all.

I am one of the authors of Apache Commons Secure XML, an Apache Commons component whose only purpose is to provide secure-by-default JAXP factories. Writing it meant working through the per-implementation differences in detail, and that turned up a number of places where this page is out of date or actively misleading. I would like to bring that back into the cheat sheet.

I am opening this issue first, as CONTRIBUTING.md asks for a substantially updated cheat sheet.

Why now

XXE reads like a solved problem. It is not. Apache Tika was hit twice during 2025 — CVE-2025-54988 and then CVE-2025-66516 at CVSS 10.0 — and AI-assisted reporting has since surfaced a steady stream of XXE findings against Java projects, some genuine and many merely alleged.

That changes the economics for a maintainer. Arguing case by case that a particular parser only ever sees trusted input is slow and rarely convincing to the person who filed the report. Disabling external entities wherever they are not needed is simply cheaper. That is why we started Apache Commons Secure XML in May.

The cheat sheet has a part to play on both sides of that choice, and today it serves neither well:

  • Readers who prefer to harden parsers themselves need to know the limits of each setting — which implementations actually honor it, what it does not cover, and whether it fails loudly or silently.
  • Readers who would rather not own "10 lines of code" should know that a maintained library exists and can absorb those reports on their behalf. We are ready for them.

What is missing or needs to be updated?

1. The examples fail open

Both large snippets catch ParserConfigurationException, log that "the feature is probably not supported by your XML processor", and carry on parsing with an unhardened factory. That exception is precisely the case where the recipe did not apply. This is the single most consequential problem on the page, because it teaches the wrong reflex.

2. No mention of newDefaultInstance()

JAXP factories are pluggable, so what newInstance() returns depends on the classpath, not on your code — and every feature the page recommends is optional. newDefaultInstance() (Java 9) lets an application opt out of the lookup and know which implementation it is configuring. The page never mentions it.

3. Settings recommended without justification

setExpandEntityReferences is the clearest case: it governs how entities are represented in the DOM tree, not whether they are fetched, so it does nothing for XXE. It sits in the sample code as though it were a control.

4. Missing resolver details

Resolver behavior is where the remaining sharp edges live, and the two resolver interfaces do not work the same way:

  • A SAX EntityResolver returns an InputSource. Returning null tells the parser to resolve the reference itself, which is a well-known fact.
  • A StAX XMLResolver returns Object, but only three types are honoured: InputStream, XMLStreamReader or XMLEventReader. Anything else is undefined, and the built-in JDK parser treats it as null — and fetches the entity.

Apache Tika hit exactly this, which caused CVE-2025-54988 and CVE-2025-66516.

5. Ten-year-old warnings

  • Spring: CVE-2013-4152 and CVE-2013-7315 affect Spring Framework 3.0.0–3.2.3 and 4.0.0.M1–M2 — long end-of-life. Both links point at pivotal.io, which no longer resolves.
  • Castor: the advice is to upgrade past 1.3.3. This project's own Deserialization Cheat Sheet already describes Castor as "abandoned with no commits since 2016."

6. No JDK 22+ settings

jdk.xml.dtd.support (allow/ignore/deny, JDK-8306632) is the one switch that works uniformly across DOM, SAX, StAX, validation and transformation on the built-in JDK parsers. It is absent from the page.

7. Length, and the ratio of code to guidance

The Java section alone is half the document and is nearly 60% code — against a series median of 8%. GUIDELINE.md asks for concise, opinionated guidance, and language-specific sheets are expected to carry code, but 16 fenced blocks for what is essentially one recipe applied to several APIs is well past that.

8. No mention of Commons Secure XML

Of course, one of the goals is to make users aware of Apache Commons Secure XML. My suggestion is a short pointer, not a promotion — the honest framing being that this configuration is fiddly enough that most projects end up writing an XmlUtils class, and a maintained library that does only this is worth knowing about. It would link the project site and its threat model, which documents what is guaranteed and what is out of scope.

How should this be resolved?

Reorganize by what kind of object the reader is holding, rather than by API name:

  1. Pick the implementation — pluggability, and newDefaultInstance() as the way to opt out
  2. Fail closed — an unrecognized feature means nothing was hardened; plus the resolver fallback
  3. DOM / SAX / StAX — one settings table each, with a column for which implementations actually recognize this
  4. Oracle DOM Parser — a separate implementation with its own API
  5. Parsers that wrap a JAXP parser — dom4j, JDOM: hand them a reader you hardened
  6. Interfaces that need a parser — TrAX, validation, XPath and JAXB
  7. Secure JAXP factory sources — a place for secure wrappers like Apache Commons Secure XML

Every claim sourced to a primary source: JDK Javadoc, the java.xml module summary, the JAXP Security Guide, Xerces documentation, Android's own reference, and OpenJDK source where no Javadoc states the behavior.