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

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

commit e91fb9dc34c10626faed5daa620bc2ac5ed0db04
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Sat Apr 25 15:15:46 2026 +0200

    fix: SaxonProvider
---
 .../xml/factory/internal/SaxonProvider.java        | 27 ++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git 
a/src/main/java/org/apache/commons/xml/factory/internal/SaxonProvider.java 
b/src/main/java/org/apache/commons/xml/factory/internal/SaxonProvider.java
index e4faa2f..4298b05 100644
--- a/src/main/java/org/apache/commons/xml/factory/internal/SaxonProvider.java
+++ b/src/main/java/org/apache/commons/xml/factory/internal/SaxonProvider.java
@@ -43,17 +43,44 @@
  */
 public final class SaxonProvider extends AbstractXmlProvider {
 
+    /**
+     * A Saxon {@link Configuration} that locks down every channel through 
which Saxon would otherwise reach external resources.
+     *
+     * <p>Three layers of restriction are applied:</p>
+     *
+     * <ol>
+     *   <li><b>SAX layer.</b> Stylesheet and source-document reading is 
routed to {@link #makeParser} via the {@link #HARDENED} sentinel, which returns 
an
+     *   {@link XMLReader} from a hardened {@link SAXParserFactory}. DOCTYPE, 
external entities and XInclude are refused at parse time.</li>
+     *   <li><b>URI-resolution layer.</b> {@link Feature#ALLOWED_PROTOCOLS} is 
set to the empty string. This blocks XSLT inclusions {@code xsl:include},
+     *   {@code xsl:import}, {@code xsl:source-document}, and the XPath/XSLT 
functions {@code fn:doc}, {@code fn:document}, {@code fn:unparsed-text},
+     *   {@code fn:collection}, {@code fn:json-doc} and {@code 
fn:transform}.</li>
+     *   <li><b>Extension-function layer.</b> {@link 
Feature#ALLOW_EXTERNAL_FUNCTIONS} is disabled, so reflection-based extension 
calls cannot be used to
+     *       sidestep the URI restrictions.</li>
+     * </ol>
+     */
     private static class HardenedConfiguration extends  Configuration {
         private static final String HARDENED = "hardened";
         private final SAXParserFactory factory;
 
         private HardenedConfiguration(final SAXParserFactory factory) {
             this.factory = factory;
+            // SAX layer: register the HARDENED sentinel for both stylesheet 
reading and source-document reading. Saxon will call makeParser(HARDENED) below
+            // and receive an XMLReader from the supplied hardened 
SAXParserFactory.
             setStyleParserClass(HARDENED);
             setSourceParserClass(HARDENED);
+            // Extension-function layer: turn off Saxon's reflection-based 
extension calls. Without this an attacker could bypass URI restrictions through
+            // user-supplied Java extensions.
             setBooleanProperty(Feature.ALLOW_EXTERNAL_FUNCTIONS, false);
+            // URI-resolution layer: empty string disallows every URI scheme. 
Saxon front-ends the existing ResourceResolver with a ProtocolRestrictor; a
+            // later setResourceResolver call would cancel this filter, so 
this stays last in the constructor.
+            setConfigurationProperty(Feature.ALLOWED_PROTOCOLS, "");
         }
 
+        /**
+         * Saxon's hook for instantiating a SAX parser by class name.
+         *
+         * <p>We use the {@value HARDENED} sentinel value to return a hardened 
parser instead.</p>
+         */
         @Override
         public XMLReader makeParser(String className) throws 
TransformerFactoryConfigurationError {
             if (HARDENED.equals(className)) {

Reply via email to