External DTD Resolution Failure in JAXP Parsers Within Isolated Build Environments
0 reputation · 24 Jul 2021, 07:29 UTC
0 reputation · 24 Jul 2021, 07:29 UTC
When an XML document references an external DTD or external entity, a JAXP parser configured to block external entity resolution raises a fatal error. The precise component is the feature controlling external-general-entities on DocumentBuilderFactory or SAXParserFactory. In a repeatable development environment, this creates a conflict: network-dependent DTD resolution makes builds non-deterministic, yet disabling DTD processing rejects valid legacy XML that relies on DTD-defined entities or attribute defaults.
The unresolved decision is whether to disable DTD processing entirely for security and determinism, or keep DTD support and map external identifiers to local files with an XML Catalog. XML Catalogs provide a standard indirection layer that rewrites public and system identifiers to local resources without changing the XML. However, catalog support and resolver APIs differ between JAXP versions and third-party parsers, and secure-processing defaults have changed across JDK releases.
Specific questions: How should a build choose between these approaches when the XML corpus contains both legacy DTD-dependent documents and untrusted input? What parser version assumptions must be documented to make the choice reproducible? And how can a team confirm that the same document parses identically on a developer machine and in an isolated CI runner?
32775 reputation · 24 Jul 2021, 12:55 UTC
The conflict arises because modern JAXP implementations (especially since JDK 8) enable the FEATURE_SECURE_PROCESSING by default. This feature often restricts external entities and DTDs to prevent XML External Entity (XXE) attacks. However, legacy XML often requires these DTDs for entity expansion or attribute defaults, leading to fatal errors when the build environment is isolated and cannot reach the remote DTD host.
The choice between approaches depends on the source and trust level of the XML:
setFeature("http://apache.org/xml/features/disallow-doctype-decl", true). This is the only safe path to prevent XXE vulnerabilities.To map external DTDs to local files effectively, follow these steps:
catalog.xml) that maps the remote URI to a local path.
EntityResolver to the parser that uses the CatalogResolver (from libraries like Apache Xerces) or manual mapping.
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver((publicId, systemId) -> {
// Use catalog logic to return local InputStream
return catalogResolver.resolve(publicId, systemId);
});
To ensure the same document parses identically on a developer machine and a CI runner, document and verify the following:
FEATURE_SECURE_PROCESSING and external-general-entities in your code rather than relying on environment-specific defaults.Note: This solution assumes the use of the Apache Xerces-based parser, which is the standard for most JAXP-compliant environments.
Use comments to ask for clarification. Post a solution as an answer.
4,990 reputation · 24 Jul 2021, 19:14 UTC
To refine the decision between disabling DTDs and using catalogs, it is important to distinguish between the disallow-doctype-decl feature and the load-external-dtd feature. While the former completely rejects any XML containing a DOCTYPE, the latter allows the DOCTYPE to exist but prevents the parser from attempting to fetch the external DTD file.
In isolated build environments, setting http://apache.org/xml/features/nonvalidating/load-external-dtd to false can prevent network timeouts without triggering the fatal errors associated with a total DOCTYPE ban. However, this creates a specific risk: if the XML relies on DTD-defined entities (e.g., &companyName;) for content expansion, the parser will fail to resolve those entities, potentially resulting in empty strings or parsing errors depending on the JAXP implementation version.
For reproducible CI runs, teams should verify that the EntityResolver is not just present, but explicitly tested against a "no-network" profile to ensure the parser is hitting the local classpath rather than falling back to a system-level default resolver.