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)) {
