Skip to content

COMMONSXML-6: XMLInputFactory: capability-driven hardening - #12

Merged
garydgregory merged 2 commits into
apache:mainfrom
ppkarwasz:feature/stax-feature-based-hardening
Jul 5, 2026
Merged

COMMONSXML-6: XMLInputFactory: capability-driven hardening#12
garydgregory merged 2 commits into
apache:mainfrom
ppkarwasz:feature/stax-feature-based-hardening

Conversation

@ppkarwasz

Copy link
Copy Markdown
Member

Applies the COMMONSXML-1 capability-driven approach to the StAX path, replacing the per-implementation class-name dispatch for XMLInputFactory with a single StaxHardener.

  • StAX has no single standard knob like JAXP 1.5 ACCESS_EXTERNAL_*, so StaxHardener.harden consolidates the JDK Zephyr and Woodstox recipes into one pass: apply limits and Zephyr ignore-external-dtd best-effort, try the Woodstox resolver trio, and fall back to a single deny-all XMLResolver when that trio is not accepted.
  • External entities stay on and are denied via resolvers, leaving the standard SUPPORT_DTD / IS_SUPPORTING_EXTERNAL_ENTITIES defaults untouched, so both bundled parsers keep their original throw-on-external-entity behavior.
  • Limits.tryApply(XMLInputFactory) replaces applyToJdkStax/applyToWoodstox; JaxpSetters gains trySetProperty(XMLInputFactory) and setOptionalProperty(XMLInputFactory). WoodstoxProvider and StockJdkProvider.configure(XMLInputFactory) are removed; XmlFactories.newXMLInputFactory() routes through StaxHardener. The DTD_SUBSET_ONLY resolver and its SpotBugs known-null exclusion move with it.

Behaviour: an unrecognized StAX implementation is now hardened best-effort (deny-all XMLResolver) instead of being rejected; on such an implementation that fallback also denies the external subset, so a DOCTYPE-only document may fail (fail-closed). The StAX attack tests are unchanged. mvn verify green across the stock JDK (Zephyr) and Woodstox profiles.

Replace the per-implementation class-name dispatch for StAX with a single
capability-driven recipe, mirroring the DocumentBuilderFactory and
SAXParserFactory rework. A new StaxHardener consolidates the JDK Zephyr and
Woodstox recipes into one pass that probes which properties each factory
accepts and adapts:

- Limits are applied best-effort by Limits.tryApply(XMLInputFactory), which sets
  both the JDK URL limit properties and the Woodstox com.ctc.wstx.* properties;
  each implementation honours its own and rejects the other's.
- The external DTD subset is skipped via Zephyr's ignore-external-dtd
  (best-effort) so a DOCTYPE-only document still parses without a fetch attempt.
- External entities are denied through resolvers, leaving the standard
  SUPPORT_DTD / IS_SUPPORTING_EXTERNAL_ENTITIES defaults untouched. Woodstox
  exposes fine-grained hooks (dtdResolver, entityResolver,
  undeclaredEntityResolver), so when all three apply the factory is Woodstox;
  any factory that does not accept that trio (the JDK Zephyr, or an
  unrecognized implementation) instead gets a single deny-all XMLResolver via
  setXMLResolver.

JaxpSetters gains trySetProperty(XMLInputFactory) and
setOptionalProperty(XMLInputFactory). Limits.tryApply(XMLInputFactory) replaces
applyToJdkStax and applyToWoodstox. WoodstoxProvider and
StockJdkProvider.configure(XMLInputFactory) are removed;
XmlFactories.newXMLInputFactory() routes through StaxHardener. The DTD_SUBSET_ONLY
resolver moves to StaxHardener, and its SpotBugs known-null exclusion moves with
it.

An implementation is no longer rejected for being unrecognized: it is hardened
best-effort, with the deny-all XMLResolver as the fallback. The throw-on-external
-entity behaviour of both bundled parsers is preserved, so the StAX attack tests
are unchanged.

Assisted-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@garydgregory

Copy link
Copy Markdown
Member

The Jira ticket is COMMONSXML-6

@garydgregory

Copy link
Copy Markdown
Member

Hi @ppkarwasz
To move things along, I merged #11 but now this one has conflicts. Would you please review and resolve? TY!

@ppkarwasz

Copy link
Copy Markdown
Member Author

@garydgregory,

I resolved the conflicts in both this and #13.

I will also submit one last PR of this series that refactors TransformerFactory support into a single class too. That will leave us with:

  • The three discussions about adding or removing features: COMMONSXML-2, COMMONSXML-3 and COMMONSXML-4,
  • The changes I added to copernik-xml-factory version 0.1.2, which mainly consisted in adding a non-overrideable floor to the resolvers. Users are still free to call setXResolver, but that call will not replace our resolver, only chain the user-provided resolver in front of ours. Returning null will fall back to a deny-all or ignore-all.

@garydgregory
garydgregory merged commit 7a43ed0 into apache:main Jul 5, 2026
15 checks passed
@garydgregory

Copy link
Copy Markdown
Member

Merged, thank you @ppkarwasz 🚀

@garydgregory

Copy link
Copy Markdown
Member

@ppkarwasz
Hi. #13 still has conflicts after merging this PR.

@ppkarwasz
ppkarwasz deleted the feature/stax-feature-based-hardening branch July 5, 2026 20:18
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