This is an automated email from the ASF dual-hosted git repository. ppkarwasz pushed a commit to branch feat/use-commons-xml in repository https://gitbox.apache.org/repos/asf/commons-jxpath.git
commit f9af9ef672052f4425ed4af277328cc1e69587bc Author: Piotr P. Karwasz <[email protected]> AuthorDate: Mon Aug 31 15:25:33 2026 +0200 Harden XML parsing via commons-secure-xml Create XML parsers and transformers through org.apache.commons:commons-secure-xml. The secure 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. Changes: - Add the commons-secure-xml dependency (1.0.0-SNAPSHOT until its first release). - Route factory creation through SecureDocumentBuilderFactory in DOMParser and SecureTransformerFactory in XMLDocumentContainer; the caller-configurable factory settings (validation, namespace awareness, entity expansion, whitespace, comments, coalescing) keep working. - JDOMParser builds its SAX reader through the secure factory as well, by overriding SAXBuilder.createParser(); documents with internal DTD subsets parse as before. - Parsers registered through DocumentContainer.registerXMLParser remain under the control of their authors. - Run the CI and CodeQL builds with -Puse-apache-snapshots (inherited from the org.apache:apache parent POM) so the commons-secure-xml SNAPSHOT resolves; CodeQL's autobuild receives the profile through MAVEN_ARGS. Assisted-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MHgnMnGWHQoH2zD2jFdoMT --- .github/workflows/codeql-analysis.yml | 2 ++ .github/workflows/maven.yml | 2 +- pom.xml | 5 +++++ src/changes/changes.xml | 1 + .../apache/commons/jxpath/XMLDocumentContainer.java | 4 ++-- .../java/org/apache/commons/jxpath/xml/DOMParser.java | 3 ++- .../org/apache/commons/jxpath/xml/JDOMParser.java | 19 ++++++++++++++++++- 7 files changed, 31 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 38e64244..8874b24b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -70,6 +70,8 @@ jobs: # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@5595ccaf912efad79be6eef63a5619ff05969be3 # 4.37.6 + env: + MAVEN_ARGS: -Puse-apache-snapshots # âšī¸ Command-line programs to run using the OS shell. # đ https://git.io/JvXDl diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 8a491731..55707fe3 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -59,4 +59,4 @@ jobs: distribution: ${{ runner.os == 'macOS' && matrix.java == '8' && 'zulu' || 'temurin' }} java-version: ${{ matrix.java }} - name: Build with Maven - run: mvn --errors --show-version --batch-mode --no-transfer-progress -DtrimStackTrace=false + run: mvn --errors --show-version --batch-mode --no-transfer-progress -DtrimStackTrace=false -Puse-apache-snapshots diff --git a/pom.xml b/pom.xml index 1b4a067b..47197002 100644 --- a/pom.xml +++ b/pom.xml @@ -149,6 +149,11 @@ </dependencies> </dependencyManagement> <dependencies> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-secure-xml</artifactId> + <version>1.0.0-SNAPSHOT</version> + </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 7cc672ce..d9c1e661 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -49,6 +49,7 @@ The <action> type attribute can be add,update,fix,remove. <!-- The release date is the date RC is cut --> <release version="1.4.1" date="YYYY-MM-DD" description="This is a maintenance release. Java 8 or later is required."> <!-- FIX --> + <action type="fix" dev="pkarwasz">Create the DOM and JDOM parsers and the XML transformer through org.apache.commons:commons-secure-xml, so external entities and DTDs are no longer fetched by default.</action> <action type="fix" dev="ggregory" due-to="Gary Gregory">POM assembly:single does not generate binary convenience files (tar/zip).</action> <action type="fix" dev="ggregory" due-to="Dima1224, Gary Gregory">Make dynamicPropertyHandlerMap in ValueUtils thread-safe #251.</action> <action type="fix" dev="ggregory" due-to="Gary Gregory">Refactor JXPathIntrospector internal static maps to use concurrent classes instead of synchronization.</action> diff --git a/src/main/java/org/apache/commons/jxpath/XMLDocumentContainer.java b/src/main/java/org/apache/commons/jxpath/XMLDocumentContainer.java index cb09f657..c8001586 100644 --- a/src/main/java/org/apache/commons/jxpath/XMLDocumentContainer.java +++ b/src/main/java/org/apache/commons/jxpath/XMLDocumentContainer.java @@ -22,10 +22,10 @@ import java.util.Objects; import javax.xml.transform.Source; import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMResult; import org.apache.commons.jxpath.xml.DocumentContainer; +import org.apache.commons.xml.secure.SecureTransformerFactory; /** * An XML document container reads and parses XML only when it is accessed. JXPath traverses Containers transparently - you use the same paths to access objects @@ -85,7 +85,7 @@ public class XMLDocumentContainer implements Container { try { if (source != null) { final DOMResult result = new DOMResult(); - final Transformer trans = TransformerFactory.newInstance().newTransformer(); + final Transformer trans = SecureTransformerFactory.newInstance().newTransformer(); trans.transform(source, result); document = result.getNode(); } else { diff --git a/src/main/java/org/apache/commons/jxpath/xml/DOMParser.java b/src/main/java/org/apache/commons/jxpath/xml/DOMParser.java index 26e7c69d..35796d5b 100644 --- a/src/main/java/org/apache/commons/jxpath/xml/DOMParser.java +++ b/src/main/java/org/apache/commons/jxpath/xml/DOMParser.java @@ -22,6 +22,7 @@ import java.io.InputStream; import javax.xml.parsers.DocumentBuilderFactory; import org.apache.commons.jxpath.JXPathException; +import org.apache.commons.xml.secure.SecureDocumentBuilderFactory; /** * An implementation of the XMLParser interface that produces a DOM Document. @@ -38,7 +39,7 @@ public class DOMParser extends XMLParser2 { @Override public Object parseXML(final InputStream stream) { try { - final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + final DocumentBuilderFactory factory = SecureDocumentBuilderFactory.newInstance(); factory.setValidating(isValidating()); factory.setNamespaceAware(isNamespaceAware()); factory.setIgnoringElementContentWhitespace(isIgnoringElementContentWhitespace()); diff --git a/src/main/java/org/apache/commons/jxpath/xml/JDOMParser.java b/src/main/java/org/apache/commons/jxpath/xml/JDOMParser.java index a10122cd..bddea63a 100644 --- a/src/main/java/org/apache/commons/jxpath/xml/JDOMParser.java +++ b/src/main/java/org/apache/commons/jxpath/xml/JDOMParser.java @@ -19,8 +19,13 @@ package org.apache.commons.jxpath.xml; import java.io.InputStream; +import javax.xml.parsers.SAXParserFactory; + import org.apache.commons.jxpath.JXPathException; +import org.apache.commons.xml.secure.SecureSAXParserFactory; +import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; +import org.xml.sax.XMLReader; /** * An implementation of the XMLParser interface that produces a JDOM Document. @@ -40,7 +45,19 @@ public class JDOMParser extends XMLParser2 { throw new JXPathException("JDOM parser configuration error. JDOM does not support the namespaceAware=false setting."); } try { - final SAXBuilder builder = new SAXBuilder(); + // JDOM builds its reader through JAXP internally; hand it one from the secure factory instead. + final SAXBuilder builder = new SAXBuilder() { + @Override + protected XMLReader createParser() throws JDOMException { + try { + final SAXParserFactory factory = SecureSAXParserFactory.newNSInstance(); + factory.setValidating(isValidating()); + return factory.newSAXParser().getXMLReader(); + } catch (final Exception ex) { + throw new JDOMException("Unable to create a new XML reader", ex); + } + } + }; builder.setExpandEntities(isExpandEntityReferences()); builder.setIgnoringElementContentWhitespace(isIgnoringElementContentWhitespace()); builder.setValidation(isValidating());
