This is an automated email from the ASF dual-hosted git repository.

garydgregory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/commons-secure-xml.git


The following commit(s) were added to refs/heads/main by this push:
     new 87098ea  Document JAXB (#103)
87098ea is described below

commit 87098eab14b6393dc3d79e24a0e758b7c1603c6a
Author: Gary Gregory <[email protected]>
AuthorDate: Fri Sep 25 09:25:18 2026 -0400

    Document JAXB (#103)
    
    * Document JAXB
    
    * Document JAXB
    
    * Exclude JAXBExamples from Android tests.
    
    * Checkstyle
    
    * Convert JaxbExample into JaxbTest
    
    * Update comments and docs.
    
    * Refactor pom.xml to extarct dependency versions into maven properties
    
    * Remove old file.
    
    * Fix Graal
    
    * Fix Graal
---
 android-tests/build.gradle.kts                     |  4 +-
 pom.xml                                            | 19 +++++
 src/main/javadoc/overview.html                     | 67 +++++++++++++++
 .../org/apache/commons/xml/secure/JaxbTest.java    | 95 ++++++++++++++++++++++
 4 files changed, 183 insertions(+), 2 deletions(-)

diff --git a/android-tests/build.gradle.kts b/android-tests/build.gradle.kts
index f323b98..cfa0490 100644
--- a/android-tests/build.gradle.kts
+++ b/android-tests/build.gradle.kts
@@ -103,9 +103,9 @@ android {
     }
 }
 
-// ShadingFootprintTest is a JVM-only build check (it uses jdependency to read 
target/classes); exclude it from the Android test compile.
+// These are JVM-only test classes; exclude them from the Android test compile.
 tasks.withType<JavaCompile>().configureEach {
-    exclude("**/ShadingFootprintTest.java")
+    exclude("**/ShadingFootprintTest.java", "**/JaxbTest.java")
 }
 
 // Skip JAXP groups whose factories Android does not ship
diff --git a/pom.xml b/pom.xml
index a3108d6..7666d35 100644
--- a/pom.xml
+++ b/pom.xml
@@ -73,6 +73,9 @@ limitations under the License.
     <commons.xerces.version>2.12.2</commons.xerces.version>
     <!-- Test-only: computes each secure class's shade closure, mirroring 
maven-shade minimizeJar, for ShadingFootprintTest. -->
     <commons.jdependency.version>2.16</commons.jdependency.version>
+    <!-- Test-only JAXB dependencies. -->
+    <commons.jaxb.api.version>2.3.1</commons.jaxb.api.version>
+    <commons.jaxb.runtime.version>2.3.9</commons.jaxb.runtime.version>
     <!--
       jacoco-maven-plugin: Should only get better.
 
@@ -116,6 +119,19 @@ limitations under the License.
       <version>${commons.jdependency.version}</version>
       <scope>test</scope>
     </dependency>
+    <!-- JAXB examples. -->
+    <dependency>
+      <groupId>javax.xml.bind</groupId>
+      <artifactId>jaxb-api</artifactId>
+      <version>${commons.jaxb.api.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.glassfish.jaxb</groupId>
+      <artifactId>jaxb-runtime</artifactId>
+      <version>${commons.jaxb.runtime.version}</version>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
   <build>
     <defaultGoal>clean checkstyle:check spotbugs:check pmd:check 
javadoc:javadoc verify</defaultGoal>
@@ -716,6 +732,9 @@ limitations under the License.
               <buildArgs>
                 <!-- The JUnit platform feature discovers tests while the 
image is built, which initializes TestTag. -->
                 
<buildArg>--initialize-at-build-time=org.junit.platform.engine.TestTag</buildArg>
+                <!-- LocatorImpl has no static fields or initializer and is 
safe to retain in the image heap. -->
+                
<buildArg>--initialize-at-build-time=org.xml.sax.helpers.LocatorImpl</buildArg>
+                
<buildArg>--initialize-at-build-time=org.xml.sax.helpers.DefaultHandler</buildArg>
                 <!-- Embed the fixtures explicitly: the agent records only 
resources a run actually opened. -->
                 <buildArg>-H:IncludeResources=leaked/.*</buildArg>
               </buildArgs>
diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html
index f2df1c0..addc991 100644
--- a/src/main/javadoc/overview.html
+++ b/src/main/javadoc/overview.html
@@ -529,6 +529,73 @@ <h1>
       <a href="../threat_model.html">Threat Model</a> documents the resulting 
contract.
     </p>
   </section>
+  <section id="jaxb">
+    <h1>
+      <img src="org/apache/commons/xml/secure/doc-files/leaf.svg" 
style="height: 1em; padding-right: 0.25em" alt="leaf">Migrating JAXB
+    </h1>
+    This section shows you how to secure XML parsing and JAXB unmarshalling.
+    <p>
+      JAXB does not provide a portable guarantee that external entity 
resolution is disabled or that Billion Laughs entity-expansion payloads are 
bounded by default.
+      Below are the two recommended approaches for integrating <strong>Apache 
Commons Secure XML</strong> to harden your unmarshalling pipeline.
+    </p>
+    <h2>Option 1: SAX-based Secure Unmarshalling (Recommended)</h2>
+    <p>
+      This approach uses
+      <code>SecureSAXParserFactory</code>
+      to construct a hardened, secure
+      <code>XMLReader</code>
+      which is then wrapped inside a
+      <code>SAXSource</code>
+      .
+    </p>
+    <pre>
+public MyJaxbModel unmarshalSecurelyWithSax(InputStream xmlStream) throws 
Exception {
+    JAXBContext context = JAXBContext.newInstance(MyJaxbModel.class);
+    Unmarshaller unmarshaller = context.createUnmarshaller();
+
+    // Create a secure SAXParserFactory via Apache Commons Secure XML
+    SAXParserFactory spf = SecureSAXParserFactory.newDefaultNSInstance();
+    
+    // Generate a hardened XMLReader and wrap the input source
+    XMLReader xmlReader = spf.newSAXParser().getXMLReader();
+    SAXSource source = new SAXSource(xmlReader, new InputSource(xmlStream));
+
+    // With the default resolver configuration, external DTDs and entities are 
prevented from being fetched or resolved, protecting against XXE attacks, 
protecting against XXE attacks.
+    // Entity-expansion limits use FEATURE_SECURE_PROCESSING on JDK parsers;
+    // Android support is implementation-dependent and best-effort.
+    return (MyJaxbModel) unmarshaller.unmarshal(source);
+}
+</pre>
+    <h2>Option 2: StAX-based Secure Unmarshalling</h2>
+    <p>
+      This approach uses
+      <code>SecureXMLInputFactory</code>
+      to instantiate a secure
+      <code>XMLStreamReader</code>
+      cursor stream.
+    </p>
+    <pre>
+public MyJaxbModel unmarshalSecurelyWithStax(InputStream xmlStream) throws 
Exception {
+    JAXBContext context = JAXBContext.newInstance(MyJaxbModel.class);
+    Unmarshaller unmarshaller = context.createUnmarshaller();
+
+    // Create a secure XMLInputFactory via Apache Commons Secure XML
+    XMLInputFactory xif = SecureXMLInputFactory.newDefaultFactory();
+    
+    // Create a hardened cursor reader
+    XMLStreamReader xmlReader = xif.createXMLStreamReader(xmlStream);
+
+    try {
+        // With the default resolver configuration, external DTDs and entities 
are prevented from being fetched or resolved, protecting against XXE attacks, 
protecting against XXE attacks.
+        // Entity-expansion protection is implementation-dependent and 
best-effort
+        // because StAX exposes no secure-processing feature.
+        return (MyJaxbModel) unmarshaller.unmarshal(xmlReader);
+    } finally {
+        xmlReader.close();
+    }
+}
+</pre>
+  </section>
   <section id="openrewrite-recipe">
     <h1>
       <img src="org/apache/commons/xml/secure/doc-files/leaf.svg" 
style="height: 1em; padding-right: 0.25em" alt="leaf">Migrating to Apache 
Commons Secure
diff --git a/src/test/java/org/apache/commons/xml/secure/JaxbTest.java 
b/src/test/java/org/apache/commons/xml/secure/JaxbTest.java
new file mode 100644
index 0000000..42546b6
--- /dev/null
+++ b/src/test/java/org/apache/commons/xml/secure/JaxbTest.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml.secure;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.transform.sax.SAXSource;
+
+import org.junit.jupiter.api.Test;
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLReader;
+
+/**
+ * Tests JAXB integration.
+ * <p>
+ * This class' two public methods serve as examples for the Javadoc {@code 
overview.html} file.
+ * </p>
+ */
+public class JaxbTest {
+
+    @XmlRootElement
+    static class MyJaxbModel {
+        // JAXB model fields and methods
+    }
+
+    @Test
+    void testUnmarshalSecurelyWithSax() throws Exception {
+        try (InputStream xmlStream = new 
ByteArrayInputStream("<myJaxbModel/>".getBytes(StandardCharsets.UTF_8))) {
+            assertNotNull(new JaxbTest().unmarshalSecurelyWithSax(xmlStream));
+        }
+    }
+
+    @Test
+    void testUnmarshalSecurelyWithStax() throws Exception {
+        try (InputStream xmlStream = new 
ByteArrayInputStream("<myJaxbModel/>".getBytes(StandardCharsets.UTF_8))) {
+            assertNotNull(new JaxbTest().unmarshalSecurelyWithStax(xmlStream));
+        }
+    }
+
+    public MyJaxbModel unmarshalSecurelyWithSax(final InputStream xmlStream) 
throws Exception {
+        final JAXBContext context = JAXBContext.newInstance(MyJaxbModel.class);
+        final Unmarshaller unmarshaller = context.createUnmarshaller();
+        // Create a secure SAXParserFactory via Apache Commons Secure XML
+        final SAXParserFactory spf = 
SecureSAXParserFactory.newDefaultNSInstance();
+        // Generate a hardened XMLReader and wrap the input source
+        final XMLReader xmlReader = spf.newSAXParser().getXMLReader();
+        final SAXSource source = new SAXSource(xmlReader, new 
InputSource(xmlStream));
+        // With the default resolver configuration, external DTDs and entities 
are prevented from being fetched or resolved, protecting against XXE attacks, 
protecting against XXE attacks.
+        // Entity-expansion limits use FEATURE_SECURE_PROCESSING on JDK 
parsers;
+        // Android support is implementation-dependent and best-effort.
+        return (MyJaxbModel) unmarshaller.unmarshal(source);
+    }
+
+    public MyJaxbModel unmarshalSecurelyWithStax(final InputStream xmlStream) 
throws Exception {
+        final JAXBContext context = JAXBContext.newInstance(MyJaxbModel.class);
+        final Unmarshaller unmarshaller = context.createUnmarshaller();
+        // Create a secure XMLInputFactory via Apache Commons Secure XML
+        final XMLInputFactory xif = SecureXMLInputFactory.newDefaultFactory();
+        // Create a hardened cursor reader
+        final XMLStreamReader xmlReader = xif.createXMLStreamReader(xmlStream);
+        try {
+            // With the default resolver configuration, external DTDs and 
entities are prevented from being fetched or resolved, protecting against XXE 
attacks, protecting against XXE attacks.
+            // Entity-expansion protection is implementation-dependent and 
best-effort
+            // because StAX exposes no secure-processing feature.
+            return (MyJaxbModel) unmarshaller.unmarshal(xmlReader);
+        } finally {
+            xmlReader.close();
+        }
+    }
+}

Reply via email to