OPENNLP-1957: Use Apache Commons Secure XML for JAXP factory creation - #1306
Merged
Merged
Conversation
Replace the hand-rolled XXE hardening in `XmlUtil` with the secure-by-default factories of Apache Commons Secure XML, which enables secure processing and blocks external DTD, entity, schema and XInclude fetches on every supported JAXP implementation, including Android. The public API of `XmlUtil` is unchanged; callers in `opennlp-runtime` and `opennlp-formats` need no changes. New tests check that external entities are not resolved through either the DOM or the SAX path, that entity expansion is bounded, that the SAX parser is namespace-aware and that neither parser is XInclude-aware. Assisted-By: Claude Fable 5.1 <noreply@anthropic.com>
Add forbiddenapis signatures for the static factory methods of `DocumentBuilderFactory`, `SAXParserFactory`, `XMLInputFactory`, `TransformerFactory`, `SchemaFactory`, `XPathFactory` and `XMLReaderFactory`, so that every parser goes through `XmlUtil` or Apache Commons Secure XML. Migrate the remaining call sites: `XmlUtil.createXPath()` replaces the `XPathFactory` lookups in `GeneratorFactory` and the NKJP readers, the dictionary serializer uses `SecureTransformerFactory` directly, and the tests use the secure factories as well. The plugin was bound to the `validate` phase, which runs before compilation, so on a clean checkout such as CI it skipped every module. It now runs in its default `verify` phase. Assisted-By: Claude Fable 5.1 <noreply@anthropic.com>
ppkarwasz
force-pushed
the
OPENNLP-1957
branch
from
September 16, 2026 07:04
dd2e81d to
013db85
Compare
rzo1
requested review from
jzonthemtn,
krickert,
mawiesne and
rzo1
and removed request for
rzo1
September 16, 2026 08:31
rzo1
approved these changes
Sep 16, 2026
The `commons-secure-xml.version` property was indented with spaces in a tab-indented file. The closing `]]></signatures>` tag of the forbiddenapis block moves to column zero, matching the signature lines it closes. Assisted-By: Claude Fable 5.1 <noreply@anthropic.com>
jzonthemtn
approved these changes
Sep 16, 2026
jzonthemtn
left a comment
Contributor
There was a problem hiding this comment.
Is commons-secure-xml a new ASF project?
Member
|
Hi @jzonthemtn We recently released 1.0.0 and expected a 1.0.1 in a week or 2 based on integration feedback so far. |
mawiesne
approved these changes
Sep 17, 2026
mawiesne
left a comment
Contributor
There was a problem hiding this comment.
Thx @ppkarwasz for providing this PR.
mawiesne
reviewed
Sep 17, 2026
Contributor
FYI: There is no need to wait for OPENNLP-1880. Please let it do the StAX part. 1880 PR/changes should adapt to your set of changes. So Claudius can proceed and give us the 2nd part. |
rzo1
approved these changes
Sep 17, 2026
rzo1
left a comment
Contributor
There was a problem hiding this comment.
Thanks for clarification. I am fine with it.
`XmlUtil.createXmlInputFactory()` returns a Commons Secure XML `XMLInputFactory`, so the WN-LMF reader of OPENNLP-1880 can drop its hand-rolled hardening and rely on the same entity-resolver floor as the DOM and SAX parsers. The factory keeps the StAX defaults, so callers can still set properties such as `IS_COALESCING`. Tests check that a StAX reader does not resolve external entities, that internal entities still expand, and that caller properties are honoured. Assisted-By: Claude Fable 5.1 <noreply@anthropic.com>
The class dates from 1.8.2, the CVE-2017-12620 fix release. The StAX and XPath factory methods are new in 3.0.0. Assisted-By: Claude Fable 5.1 <noreply@anthropic.com>
A caller who only needs a secure `XMLInputFactory` can call Commons Secure XML directly. What the WN-LMF reader of OPENNLP-1880 needs is a coalescing `XMLStreamReader` that leaves the caller's stream open, so `XmlUtil` now provides exactly that from a single shared factory, wrapping the stream in `UncloseableInputStream` because the JDK reader closes it on `close()`. The factory lives in a lazy holder class, so the DOM and SAX helpers still load on a platform without StAX. Assisted-By: Claude Fable 5.1 <noreply@anthropic.com>
Member
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves OPENNLP-1957.
What
XmlUtilobtains itsDocumentBuilder,SAXParser,XMLStreamReaderandXPathfrom Apache Commons Secure XML instead of applying a hand-maintained list of vendor-specific features. The dictionary serializer usesSecureTransformerFactorydirectly. Existing callers are unchanged.validate, which runs before compilation, so it was skipped on every clean build including CI. It now runs inverify.SECURITY.mdpoints at the library's threat model.Why
After CVE-2017-12620 and CVE-2026-40682, each new XML entry point had to repeat the same OWASP checklist with per-platform fallbacks. Delegating to a component built for secure JAXP factory creation means fixes for new parser features or implementations arrive with a dependency bump.
Behavioral changes
setExpandEntityReferences(false)is no longer called. The setting is not a security control: it only changes the shape of the DOM, leaving each reference as anEntityReferencenode whose replacement text consumers may never see. It stops external entity expansion in the built-in JDK implementation only through a bug, which stops internal entity expansion as well. External entities are now blocked by the library's entity-resolver floor, and internal ones are expanded inline, bounded by secure processing.IllegalStateExceptioninstead of logging a warning. Android factories are passed through, as before.The new
XmlUtil.createXmlStreamReader(InputStream)is meant for the WN-LMF reader of OPENNLP-1880 (#1155). It returns a coalescing reader from a single shared factory and leaves the caller's stream open, so the reader can drop both itshardenedFactory()and itsNonClosingInputStream. One difference to note there: the secure factory resolves an external entity to empty content instead of throwing, unless theorg.apache.commons.xml.secure.throwOnUnresolvedsystem property is set.🤖 Generated with Claude Code