This is an automated email from the ASF dual-hosted git repository.
oscerd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new bde30bfe2801 CAMEL-23783: harden camel-schematron rules
TransformerFactory against XXE (#24105)
bde30bfe2801 is described below
commit bde30bfe280141893cd46944d0773a63d412557c
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Jun 18 12:22:41 2026 +0200
CAMEL-23783: harden camel-schematron rules TransformerFactory against XXE
(#24105)
Signed-off-by: Andrea Cosentino <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
---
.../component/schematron/SchematronEndpoint.java | 10 +++-
.../SchematronTransformerFactoryHardeningTest.java | 56 ++++++++++++++++++++++
.../src/test/resources/sch/schematron-xxe.sch | 29 +++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_21.adoc | 13 +++++
4 files changed, 107 insertions(+), 1 deletion(-)
diff --git
a/components/camel-schematron/src/main/java/org/apache/camel/component/schematron/SchematronEndpoint.java
b/components/camel-schematron/src/main/java/org/apache/camel/component/schematron/SchematronEndpoint.java
index 4a339147b22f..3234327bef3f 100644
---
a/components/camel-schematron/src/main/java/org/apache/camel/component/schematron/SchematronEndpoint.java
+++
b/components/camel-schematron/src/main/java/org/apache/camel/component/schematron/SchematronEndpoint.java
@@ -20,7 +20,9 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
+import javax.xml.XMLConstants;
import javax.xml.transform.Templates;
+import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
@@ -167,7 +169,7 @@ public class SchematronEndpoint extends DefaultEndpoint {
}
}
- private void createTransformerFactory() throws ClassNotFoundException {
+ private void createTransformerFactory() throws ClassNotFoundException,
TransformerConfigurationException {
// provide the class loader of this component to work in OSGi
environments
Class<TransformerFactory> factoryClass
=
getCamelContext().getClassResolver().resolveMandatoryClass(SAXON_TRANSFORMER_FACTORY_CLASS_NAME,
@@ -175,6 +177,12 @@ public class SchematronEndpoint extends DefaultEndpoint {
LOG.debug("Using TransformerFactoryClass {}", factoryClass);
transformerFactory =
getCamelContext().getInjector().newInstance(factoryClass);
+ // Harden the rules-compilation factory against XXE / external
resource access, consistent with the
+ // SAXParserFactory hardening in SchematronProcessorFactory. The ISO
skeleton stylesheets are resolved
+ // from the classpath via the URIResolver set below, so legitimate
rule compilation is unaffected.
+ transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING,
true);
+ transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
+
transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
transformerFactory.setURIResolver(new
ClassPathURIResolver(Constants.SCHEMATRON_TEMPLATES_ROOT_DIR,
this.uriResolver));
transformerFactory.setAttribute(LINE_NUMBERING, true);
}
diff --git
a/components/camel-schematron/src/test/java/org/apache/camel/component/schematron/SchematronTransformerFactoryHardeningTest.java
b/components/camel-schematron/src/test/java/org/apache/camel/component/schematron/SchematronTransformerFactoryHardeningTest.java
new file mode 100644
index 000000000000..eab674d79b7a
--- /dev/null
+++
b/components/camel-schematron/src/test/java/org/apache/camel/component/schematron/SchematronTransformerFactoryHardeningTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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
+ *
+ * http://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.camel.component.schematron;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Verifies that the schematron rules-compilation {@code TransformerFactory}
is hardened. Resolving a schematron
+ * endpoint compiles its rules, so these tests assert on endpoint resolution:
legitimate rules (whose ISO skeleton
+ * stylesheets resolve from the classpath via the URIResolver) still compile,
while rules referencing an external entity
+ * are refused rather than having the entity resolved.
+ */
+public class SchematronTransformerFactoryHardeningTest {
+
+ @Test
+ void legitimateRulesStillCompileWithHardenedFactory() throws Exception {
+ try (CamelContext ctx = new DefaultCamelContext()) {
+ ctx.start();
+ assertDoesNotThrow(
+ () -> ctx.getEndpoint("schematron:sch/schematron-1.sch",
SchematronEndpoint.class),
+ "Legitimate schematron rules must still compile after
enabling secure processing");
+ }
+ }
+
+ @Test
+ void externalEntityInRulesIsNotResolved() throws Exception {
+ try (CamelContext ctx = new DefaultCamelContext()) {
+ ctx.start();
+ // Without the hardening this rules file compiles (the external
entity is expanded into the assert text);
+ // with FEATURE_SECURE_PROCESSING + accessExternalDTD="" the
parser refuses the entity, so compilation
+ // (triggered on endpoint resolution) must fail instead of
resolving it.
+ assertThrows(Exception.class,
+ () -> ctx.getEndpoint("schematron:sch/schematron-xxe.sch",
SchematronEndpoint.class),
+ "Schematron rules referencing an external entity must fail
rather than resolve the entity");
+ }
+ }
+}
diff --git
a/components/camel-schematron/src/test/resources/sch/schematron-xxe.sch
b/components/camel-schematron/src/test/resources/sch/schematron-xxe.sch
new file mode 100644
index 000000000000..9f28b2dfc294
--- /dev/null
+++ b/components/camel-schematron/src/test/resources/sch/schematron-xxe.sch
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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
+
+ http://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.
+
+-->
+<!DOCTYPE sch:schema [
+ <!ENTITY xxe SYSTEM "file:///etc/hostname">
+]>
+<sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
+ <sch:pattern>
+ <sch:rule context="/">
+ <sch:assert test="true()">&xxe;</sch:assert>
+ </sch:rule>
+ </sch:pattern>
+</sch:schema>
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
index cc9ef69d2372..9110dd5d4226 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
@@ -2496,3 +2496,16 @@ and do not rely on a custom verifier can opt in to
`BOTH`.
The `CLIENT` policy is a deliberate choice: it preserves backward
compatibility and allows
`NoopHostnameVerifier` to work as documented. A future release may add an
option to opt into the
`BOTH` policy for defense-in-depth.
+
+=== camel-schematron - potential breaking change
+
+The Schematron rules-compilation `TransformerFactory` now runs with secure
processing enabled
+(`FEATURE_SECURE_PROCESSING`) and with external DTD and external stylesheet
access disabled
+(`accessExternalDTD` and `accessExternalStylesheet` set to empty), as
defense-in-depth against XXE and
+external-resource resolution while compiling Schematron rules. This matches
the hardening already applied
+to the component's `SAXParserFactory`. The bundled ISO Schematron skeleton
stylesheets continue to be
+resolved from the classpath via the component's `URIResolver` and are
therefore unaffected.
+
+If your Schematron rules legitimately reference an external DTD, external
entity, or external stylesheet,
+those references will no longer be resolved and rule compilation will fail;
inline the referenced content
+instead.