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());

Reply via email to