Skip to content

CAMEL-25827: camel-xslt-saxon - restrict external resource access for a Source message body - #28160

Open
oscerd wants to merge 1 commit into
apache:mainfrom
oscerd:fix/CAMEL-25827
Open

oscerd wants to merge 1 commit into
apache:mainfrom
oscerd:fix/CAMEL-25827

Conversation

@oscerd

@oscerd oscerd commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

CAMEL-25827: camel-xslt-saxon - restrict external resource access for a Source message body

Problem

XsltSaxonAggregationStrategy builds its Saxon TransformerFactory with the Saxon defaults: it does
not enable FEATURE_SECURE_PROCESSING and does not set ACCESS_EXTERNAL_DTD /
ACCESS_EXTERNAL_STYLESHEET. The xslt-saxon endpoint has configured those on the factory it creates
since 4.22.0, so the same stylesheet ran with different settings depending on whether it was used from
an endpoint or from the aggregation strategy.

Separately, a message body that is already a javax.xml.transform.Source still to be parsed (for
example a StreamSource, or the body after a convertBodyTo(Source.class)) reaches Saxon without the
StAX/SAX conversion that String, byte[] and InputStream bodies go through. The transformer factory
has a URIResolver installed that resolves references the document declares to other resources, so the
factory's ACCESS_EXTERNAL_* attributes do not cover that parse: the reference is resolved from the
body. This affects the aggregation strategy and the xslt-saxon endpoint (and camel-xj, which extends
it).

Change

  • Add XsltSaxonHelper.configureExternalAccess(...) (the ACCESS_EXTERNAL_DTD /
    ACCESS_EXTERNAL_STYLESHEET block the endpoint had inline) and XsltSaxonHelper.secureSource(...)
    (wraps a stream-based Source still to be parsed with a reader that does not resolve references to
    external resources, leaving a DOMSource, a StAXSource, or a reader-backed Source unchanged).
  • XsltSaxonAggregationStrategy now enables secure processing and calls configureExternalAccess(...)
    on its factory, so it is configured like the endpoint's.
  • SaxonXmlSourceHandlerFactoryImpl runs the resolved source through secureSource(...) when secure
    processing is on, so a Source body is parsed with the restricted reader. XsltSaxonBuilder threads
    the flag through, and XsltSaxonEndpoint passes its existing secureProcessing option, so
    secureProcessing=false restores the previous parsing on the endpoint.
  • The endpoint now calls the shared configureExternalAccess(...) instead of its inline block, so the
    endpoint and the strategy cannot drift apart.
  • No option is added or changed, so the catalog and the DSLs are unchanged.

A route that needs XsltSaxonAggregationStrategy to use a different TransformerFactory can still set
its own with setTransformerFactory(...).

Tests

  • XsltSaxonAggregationStrategySourceBodyTest - aggregates a Source body and checks that a resource
    named by the body is not read into the result, while a declaration internal to the document is still
    applied.
  • XsltSaxonHelperConfigurationTest - unit tests for configureExternalAccess(...) and
    secureSource(...) (stream-based source wrapped with a restricted reader; an already-parsed source
    left unchanged).
  • XsltSaxonDomSourceTest updated for the new Source handling (a stream-based body is now wrapped
    with a restricted reader; DOMSource / StAXSource pass through).
  • components/camel-xslt-saxon and components/camel-xj test suites pass.

The change is documented in docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc.

Claude Code on behalf of oscerd

🤖 Generated with Claude Code

… a Source message body

XsltSaxonAggregationStrategy created its Saxon TransformerFactory with the Saxon defaults, so the
same stylesheet ran with different settings depending on whether it was used from the strategy or
from the xslt-saxon endpoint. The strategy now configures its factory the same way the endpoint
configures the factory it creates (FEATURE_SECURE_PROCESSING enabled, ACCESS_EXTERNAL_DTD and
ACCESS_EXTERNAL_STYLESHEET set to an empty string), through a shared XsltSaxonHelper method the
endpoint now calls too.

A message body that is already a javax.xml.transform.Source still to be parsed (for example a
StreamSource, or the body after convertBodyTo(Source.class)) reaches Saxon without the StAX/SAX
conversion the other body types go through. The strategy and the xslt-saxon endpoint now parse such
a body with a reader that does not resolve references to external resources declared in the
document; a DOMSource, a StAXSource, or a Source that already carries its own reader is unchanged,
and a declaration internal to the document is still applied. On the endpoint this follows the
existing secureProcessing option.

Adds tests for the strategy and the shared helper, updates XsltSaxonDomSourceTest for the new
Source handling, and documents the change in the 4.24 upgrade guide.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
@oscerd
oscerd requested review from davsclaus and gnodet October 20, 2026 07:35
@github-actions

Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • components/camel-xslt-saxon
  • docs

🔁 3 tests passed only after a retry on JDK 25 (3 retried attempts)

Recovered flaky tests on JDK 25 (3)
Module Test Failed attempts First failure
dsl/camel-jbang/camel-jbang-plugin-tui AiPanelAcpTest.clearOpensANewSessionOnTheNextPrompt 1 expected: <2> but was: <1>
dsl/camel-jbang/camel-jbang-plugin-tui AiPanelAcpTest.retryReplaysAnAgentCommandWithoutThePrefix 1 Index 1 out of bounds for length 1
dsl/camel-jbang/camel-jbang-plugin-tui LaunchManagerOutcomeTest.startedWhenTheStatusFileSaysSo 1 expected: null but was: LaunchOutcome[ok=false, log=null]

ℹ️ These tests did not fail the build. Surefire retried them and they passed.
Retries are enabled project-wide by surefire.rerunFailingTestsCount in parent/pom.xml.


🔬 Scalpel shadow comparison — Scalpel: 10 of 707 tested, 26 compile-only — current: 10 all tested

Maveniverse Scalpel detected 10 affected modules (current approach: 10).

Skip-tests mode would test 10 modules (2 direct + 9 downstream), skip tests for 26 (generated code, meta-modules)

Modules Scalpel would test (10)
  • camel-jbang-mcp ← downstream of org.apache.camel:camel-catalog
  • camel-jbang-plugin-mcp ← downstream of org.apache.camel:camel-jbang-core
  • camel-jbang-plugin-tui ← downstream of org.apache.camel:camel-catalog
  • camel-jbang-plugin-validate ← downstream of org.apache.camel:camel-yaml-dsl-validator
  • camel-launcher-container ← downstream of org.apache.camel:camel-launcher
  • camel-spring-ws ← downstream of org.apache.camel:camel-xslt-saxon
  • camel-xj ← downstream of org.apache.camel:camel-xslt-saxon
  • camel-xslt-saxon ← components/camel-xslt-saxon/src/main/java/org/apache/camel/component/xslt/saxon/SaxonXmlSourceHandlerFactoryImpl.java, components/camel-xslt-saxon/src/main/java/org/apache/camel/component/xslt/saxon/XsltSaxonAggregationStrategy.java, components/camel-xslt-saxon/src/main/java/org/apache/camel/component/xslt/saxon/XsltSaxonBuilder.java, components/camel-xslt-saxon/src/main/java/org/apache/camel/component/xslt/saxon/XsltSaxonEndpoint.java, components/camel-xslt-saxon/src/main/java/org/apache/camel/component/xslt/saxon/XsltSaxonHelper.java, components/camel-xslt-saxon/src/test/java/org/apache/camel/component/xslt/saxon/XsltSaxonAggregationStrategySourceBodyTest.java, components/camel-xslt-saxon/src/test/java/org/apache/camel/component/xslt/saxon/XsltSaxonDomSourceTest.java, components/camel-xslt-saxon/src/test/java/org/apache/camel/component/xslt/saxon/XsltSaxonHelperConfigurationTest.java
  • camel-yaml-dsl-validator ← downstream of org.apache.camel:camel-catalog
  • camel-yaml-dsl-validator-maven-plugin ← downstream of org.apache.camel:camel-yaml-dsl-validator
Modules with tests skipped (26)
  • apache-camel
  • camel-allcomponents
  • camel-catalog
  • camel-catalog-console
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • dummy-component

ℹ️ Shadow mode — Scalpel observes but does not affect test execution. Learn more

All tested modules (37 modules, 6m 14s total)

Total reactor time: 6m 14s

Module Duration Status
Camel :: Launcher 52.2s SUCCESS
Camel :: JBang :: MCP 43.0s SUCCESS
Camel :: JBang :: Plugin :: Validate 29.3s SUCCESS
Camel :: Catalog :: Camel Catalog 23.5s SUCCESS
Camel :: YAML DSL :: Validator 23.3s SUCCESS
Camel :: YAML DSL 21.5s SUCCESS
Camel :: Spring Web Services 21.3s SUCCESS
Camel :: Component DSL 17.9s SUCCESS
Camel :: XSLT Saxon 17.7s SUCCESS
Camel :: Kamelet Main 16.0s SUCCESS
Camel :: Docs 14.7s SUCCESS
Camel :: JBang :: Plugin :: Kubernetes 12.2s SUCCESS
Camel :: JBang :: Plugin :: Testing 12.1s SUCCESS
Camel :: XJ 12.0s SUCCESS
Camel :: YAML DSL :: Deserializers 8.4s SUCCESS
Camel :: Catalog :: Camel Route Parser 7.8s SUCCESS
Camel :: Catalog :: Camel Report Maven Plugin 7.2s SUCCESS
Camel :: All Components Sync point 5.4s SUCCESS
Camel :: Assembly 5.3s SUCCESS
Camel :: YAML DSL :: Validator Maven Plugin 3.9s SUCCESS
Camel :: YAML DSL :: Maven Plugins 3.2s SUCCESS
Camel :: Catalog :: Maven 2.9s SUCCESS
Camel :: Catalog :: Suggest (deprecated) 2.8s SUCCESS
Camel :: JBang :: Plugin :: Edit 1.8s SUCCESS
Camel :: Coverage 1.3s SUCCESS
Camel :: JBang :: Plugin :: Generate 1.2s SUCCESS
Camel :: JBang :: Integration tests 1.1s SUCCESS
Camel :: Catalog :: Console 1.1s SUCCESS
Camel :: Catalog :: Dummy Component 0.9s SUCCESS
Camel :: JBang :: Main 0.7s SUCCESS
Camel :: Endpoint DSL :: Support 0.7s SUCCESS
Camel :: Launcher :: Container 0.7s SUCCESS
Camel :: JBang :: Plugin :: MCP 0.5s SUCCESS
Camel :: Endpoint DSL n/a
Camel :: Integration Tests n/a
Camel :: JBang :: Core n/a
Camel :: JBang :: Plugin :: TUI n/a

Top 20 slowest modules:

  • Camel :: Launcher (52.2s)
  • Camel :: JBang :: MCP (43.0s)
  • Camel :: JBang :: Plugin :: Validate (29.3s)
  • Camel :: Catalog :: Camel Catalog (23.5s)
  • Camel :: YAML DSL :: Validator (23.3s)
  • Camel :: YAML DSL (21.5s)
  • Camel :: Spring Web Services (21.3s)
  • Camel :: Component DSL (17.9s)
  • Camel :: XSLT Saxon (17.7s)
  • Camel :: Kamelet Main (16.0s)
  • Camel :: Docs (14.7s)
  • Camel :: JBang :: Plugin :: Kubernetes (12.2s)
  • Camel :: JBang :: Plugin :: Testing (12.1s)
  • Camel :: XJ (12.0s)
  • Camel :: YAML DSL :: Deserializers (8.4s)
  • Camel :: Catalog :: Camel Route Parser (7.8s)
  • Camel :: Catalog :: Camel Report Maven Plugin (7.2s)
  • Camel :: All Components Sync point (5.4s)
  • Camel :: Assembly (5.3s)
  • Camel :: YAML DSL :: Validator Maven Plugin (3.9s)

⚙️ View full build and test results

@davsclaus davsclaus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, the parity with the endpoint factory is welcome, and sharing configureExternalAccess(...) is a good way to stop the two from drifting apart. A few points on how the restriction is switched off and on what happens when it fails:

  1. secureProcessing now also controls the input-parsing restriction, but the option is not marked as a security knob. On XsltSaxonEndpoint (@UriParam) and XsltSaxonComponent (@Metadata) it should get label = "security" (or advanced,security), security = "insecure:dev" and insecureValue = "false", so camel.main.profile=prod reports secureProcessing=false. This is the same pattern as allowExternalEntities on camel-smooks in the same 4.24 guide. The catalog and DSL then need regenerating.
  2. Linked to 1: the javadoc of secureProcessing tells Saxon PE/EE users to turn it off to use Java extension functions. With this PR, doing that also brings back unrestricted parsing of Source bodies, and nothing tells them so. Could the parsing restriction have its own switch (like smooks' allowExternalEntities, default false, insecure:dev)? At least the option doc and the upgrade guide should say that secureProcessing=false now does both things.
  3. secureSource(...) fails open (see inline). If the restricted reader cannot be built, it logs a WARN on every exchange and transforms the body without the restriction.
  4. XsltSaxonAggregationStrategy has no opt-out for the new Source parsing. setTransformerFactory(...) only replaces the factory, while XsltSaxonBuilder keeps secureProcessing=true, so the wrapping still happens. The upgrade guide (inline) reads as if a custom factory brings back the old behaviour. Please either reword it or expose a setter on the strategy. It is also worth one sentence that FEATURE_SECURE_PROCESSING on a Saxon PE/EE factory disables reflexive Java extension functions in aggregation stylesheets.
  5. Test gap: the strategy now runs with ACCESS_EXTERNAL_STYLESHEET="", but no test covers an aggregation stylesheet that uses xsl:include / xsl:import from the classpath. The endpoint has this (SaxonXsltFeatureRouteTest), and the strategy should have it too, so a classpath include in an aggregation stylesheet stays covered.

Parity with camel-xslt looks fine: the JDK XSLTC factory that XmlConverter creates sets FEATURE_SECURE_PROCESSING, which already sets ACCESS_EXTERNAL_DTD/ACCESS_EXTERNAL_STYLESHEET to empty for the input parse, so nothing is needed there.

Claude Code on behalf of davsclaus. This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

XMLReader xmlReader = factory.newSAXParser().getXMLReader();
return new SAXSource(xmlReader, inputSource);
} catch (ParserConfigurationException | SAXException e) {
LOG.warn("Cannot create a restricted XMLReader for the input source, transforming it as is: {}",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fails open: if the SAXParserFactory on the classpath does not recognise one of the features (for example load-external-dtd is Xerces-specific), every exchange logs a WARN and the body is transformed with no restriction. Since this is the security control, I would rather fail (throw) than degrade without anyone noticing.

Also, SAXParserFactory.newInstance() runs a FactoryFinder lookup for every exchange. Consider creating and configuring the factory once, so a missing feature fails at startup, and then call only newSAXParser().getXMLReader() for each exchange.

xslt.setEntityResolver(getEntityResolver());
xslt.setAllowStAX(allowStAX);
xslt.setUseJsonBody(useJsonBody);
xslt.setSecureProcessing(secureProcessing);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this, secureProcessing also turns the input-parsing restriction on and off. Please mark the option on the endpoint and on the component with security = "insecure:dev", insecureValue = "false" (as camel-smooks does for allowExternalEntities). Better still, give the parsing restriction its own option, so that Saxon PE/EE users who turn secureProcessing off for Java extension functions do not lose it without knowing.

`byte[]`, `InputStream`) are unaffected, as they already reach the transformer through Camel's StAX/SAX
conversion.

On the `xslt-saxon` endpoint, set `secureProcessing=false` to restore the previous parsing. A route that needs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the strategy, setTransformerFactory(...) brings back the old factory settings but not the old Source parsing: XsltSaxonBuilder still wraps the body, since its secureProcessing stays true. Please either say that the strategy has no switch for the parsing, or add one. A sentence that FEATURE_SECURE_PROCESSING disables reflexive Java extension functions on Saxon PE/EE would also help users of the aggregation strategy.

@Croway

Croway commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

@oscerd there is a conflict on the upgrade guide

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants