Skip to content

Harden XML parsing via commons-xml - #647

Merged
garydgregory merged 8 commits into
masterfrom
feat/use-commons-xml
Sep 6, 2026
Merged

garydgregory merged 8 commits into
masterfrom
feat/use-commons-xml

Conversation

@ppkarwasz

@ppkarwasz ppkarwasz commented Aug 30, 2026

Copy link
Copy Markdown
Member

Replaces all direct JAXP factory instantiations (DocumentBuilderFactory, SAXParserFactory, TransformerFactory) with the secure factories from org.apache.commons:commons-xml. These enable FEATURE_SECURE_PROCESSING and install a non-removable entity-resolver floor on every parser they produce: external DTD, entity, schema and XInclude lookups that a caller-set resolver does not resolve are resolved to empty content instead of being fetched, and internal entity expansion is bounded, regardless of the JAXP implementation on the classpath.

Hardening the parsing of a configuration file is admittedly not necessary: configuration files are normally trusted. This limits the side-effects if a user (against advice) decides to parse untrusted configuration files.

Changes

  • Add the commons-xml dependency (1.0.0-SNAPSHOT until its first release) and run the CI and CodeQL builds with -Puse-apache-snapshots (inherited from the org.apache:apache parent POM) so the SNAPSHOT resolves; CodeQL's autobuild receives the profile through MAVEN_ARGS.
  • Route factory creation through SecureDocumentBuilderFactory, SecureSAXParserFactory and SecureTransformerFactory in XMLConfiguration, XMLDocumentHelper, XMLPropertiesConfiguration and XMLPropertyListConfiguration, plus the affected tests. Transformers created by SecureTransformerFactory rewrite their sources on every transform call, so no explicit hardening of the source passed to XMLDocumentHelper.transform is needed.
  • XMLConfiguration keeps its DefaultEntityResolver contract (return null for unknown entities): a null return no longer lets the parser fetch the external resource, because the resolver floor resolves it to empty content instead.
  • When schema validation is enabled, XMLConfiguration lifts the accessExternalSchema restriction to keep resolver-based schema validation (such as via CatalogResolver) working under secure processing: due to a JDK bug, schema documents supplied by an entity resolver are not marked as resolver-created (old JDK 8 versions never mark them; newer versions fail to mark only those supplied by an EntityResolver2), so the check would refuse them. Lifting the restriction does not reopen external fetches: the parser never retrieves a schema itself, because the secure factory's resolver floor resolves every lookup the caller's resolver leaves unresolved to empty content instead of fetching it.
  • TestMultiFileConfigurationBuilder#testSchemaValidationError registers the local test schema for its absolute https system URI via an XML catalog, since the hardened parser no longer fetches it; the test now reaches the intended schema validation error again.

Full test suite passes with -Puse-apache-snapshots (3051 tests); the affected test classes also pass on Java 8, where the JDK applies the accessExternalSchema check even to resolver-supplied schema documents.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NS6CoaDG2mfSNpy4Ukhvrn

@ppkarwasz
ppkarwasz force-pushed the feat/use-commons-xml branch from 18ec795 to d650a2b Compare August 30, 2026 20:33
@ppkarwasz
ppkarwasz marked this pull request as draft August 30, 2026 20:34
Replace all direct JAXP factory instantiations (DocumentBuilderFactory,
SAXParserFactory, TransformerFactory) with the secure factories from
org.apache.commons:commons-xml. These factories enable
FEATURE_SECURE_PROCESSING and install a non-removable entity-resolver
floor on every parser they produce: external DTD, entity, schema and
XInclude lookups that a caller-set resolver does not resolve are
resolved to empty content instead of being fetched, and internal entity
expansion is bounded, regardless of the JAXP implementation on the
classpath.

Hardening the parsing of a configuration file is admittedly not
necessary: configuration files are normally trusted. This limits the
side-effects if a user (against advice) decides to parse untrusted
configuration files.

Changes:
- Add the commons-xml dependency (1.0.0-SNAPSHOT until its first
  release).
- Route factory creation through SecureDocumentBuilderFactory,
  SecureSAXParserFactory and SecureTransformerFactory in
  XMLConfiguration, XMLDocumentHelper, XMLPropertiesConfiguration and
  XMLPropertyListConfiguration, plus the affected tests.
- No explicit hardening of the source passed to
  XMLDocumentHelper.transform is needed: transformers created by
  SecureTransformerFactory rewrite their sources on every transform
  call.
- XMLConfiguration keeps its DefaultEntityResolver contract (return
  null for unknown entities): a null return no longer lets the parser
  fetch the external resource, because the resolver floor resolves it
  to empty content instead.
- Keep resolver-based schema validation (for example via
  CatalogResolver) working under secure processing: disable
  EntityResolver2 handling
  (http://xml.org/sax/features/use-entity-resolver2), because the JDK
  does not mark schema documents supplied by an EntityResolver2 as
  resolver-created and its accessExternalSchema check then refuses
  them, and lift the accessExternalSchema restriction, because Java 8
  applies it even to sources supplied by a plain resolver. Lifting the
  restriction does not reopen external fetches: the secure factory's
  resolver floor still resolves every unresolved lookup to empty
  content instead of fetching it.
- Run the CI and CodeQL builds with -Puse-apache-snapshots (inherited
  from the org.apache:apache parent POM) so the commons-xml SNAPSHOT
  resolves; CodeQL's autobuild receives the profile through MAVEN_ARGS.

Assisted-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Assisted-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NS6CoaDG2mfSNpy4Ukhvrn
The 2001 test configuration references its schema through an absolute
https URI (xsi:noNamespaceSchemaLocation). The hardened parser does not
fetch external resources, so testSchemaValidationError no longer reached
the intended schema validation error; the entity resolver refused the
remote fetch first.

Register the local testMultiConfiguration.xsd for that system URI via an
XML catalog (CatalogResolver pointing at the existing catalog.xml, which
already rewrites https://commons.apache.org/ to the local test
resources) and set it as the builder's entity resolver. The schema now
loads locally, so the expected SAXParseException validation error is
raised again.

Assisted-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ppkarwasz
ppkarwasz force-pushed the feat/use-commons-xml branch from d650a2b to 071a0af Compare August 30, 2026 20:38
Disabling EntityResolver2 handling is unnecessary: the parser does use
a schema document supplied by an EntityResolver2 once the
accessExternalSchema check no longer refuses it, so lifting that
restriction alone keeps resolver-based schema validation working on
every JDK version.

Also inline XMLDocumentHelper.createTransformerFactory into its only
caller and drop its test.

Assisted-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NS6CoaDG2mfSNpy4Ukhvrn
Assisted-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NS6CoaDG2mfSNpy4Ukhvrn
@ppkarwasz
ppkarwasz marked this pull request as ready for review August 30, 2026 20:57
ppkarwasz and others added 3 commits August 31, 2026 15:15
The org.apache.commons:commons-xml artifact was renamed to
org.apache.commons:commons-secure-xml and its Java package moved from
org.apache.commons.xml to org.apache.commons.xml.secure. Update the
dependency and imports accordingly, and record the hardening in
changes.xml.

Assisted-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MHgnMnGWHQoH2zD2jFdoMT
Bump org.apache.commons:commons-secure-xml from 1.0.0-SNAPSHOT to 1.0.0
and add the temporary staging repository
https://repository.apache.org/content/repositories/orgapachecommons-1962/
after Central, so the vote gets downstream CI results. Drop the
-Puse-apache-snapshots profile from the CI workflows, which the release
version no longer needs. Remove the staging repository once 1.0.0 is
released.

Assisted-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0167e29ScPEdfzJnEFm95imK
@garydgregory
garydgregory merged commit 3e84bcb into master Sep 6, 2026
12 of 15 checks passed
@garydgregory
garydgregory deleted the feat/use-commons-xml branch September 6, 2026 13:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants