# CVE-2026-88789 — Camel Quarkus: forced Xalan `TransformerFactory` drops the JAXP external access restrictions
Runnable proof-of-concept reproducer for the Apache Camel Quarkus vulnerability where the XSLT support
extension (`camel-quarkus-support-xalan`) supplies its own Xalan-backed `TransformerFactory` to the `xslt`
component and registers it as the JAXP default. Xalan-J 2.7.x predates JAXP 1.5 and cannot honour
`javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD` or `ACCESS_EXTERNAL_STYLESHEET` — `setAttribute()` throws
`IllegalArgumentException` for both — so the external access restrictions Apache Camel applies to the
`TransformerFactory` it creates were never in effect.
| **Camel Quarkus** | [`camel-quarkus/`](camel-quarkus/) | Camel Quarkus **3.36.0** (Quarkus 3.36.0, Camel 4.20.0) |
> **Camel Quarkus only.** The vulnerable code is a Camel Quarkus extension, not a Camel component. Plain
> Camel and Camel Spring Boot use the JDK's `TransformerFactory`, which honours both attributes, so there is
> nothing to reproduce there — this repository therefore has no `camel-spring-boot/` variant.
An attacker who supplies the XML document being transformed can read local files or reach internal network
locations through an external entity declaration in that document.
curl -s
Expected output on an affected build (abridged — the driver runs six probes, see
[`camel-quarkus/README.md`](camel-quarkus/README.md)):
1) xslt endpoint, body is a StreamSource, external entity -> file:///tmp/cve-2026-88789-secrets/db-password.txt
transformation result: [db.password=LOCAL-FILE-s3cr3t-99]
2) xslt endpoint, body is a StreamSource, external entity ->
transformation result: [INTERNAL-SECRET-s3cr3t-42]
internal endpoint response in the output: true
4) CONTROL - same document as a String body (Camel converts it to a SAXSource itself)
5) TransformerFactory.newInstance() anywhere in the application
factory: org.apache.camel.quarkus.support.xalan.XalanTransformerFactory
setAttribute(ACCESS_EXTERNAL_DTD, ""): REFUSED, IllegalArgumentException: ...
identity transform of the same document: [... db.password=LOCAL-FILE-s3cr3t-99 ...]
Requests the XML parser made to internal endpoints on its own: [GET /internal/secret, GET /internal/leak.dtd]
Verified against Camel Quarkus **3.40.0** as well: every leaking probe goes quiet, the internal endpoints
receive no requests at all, and the driver prints `NOT reproduced`.
On the `xslt` component path, only bodies that reach the transformer **already as a
`javax.xml.transform.Source`** are affected. Bodies of other types — `String`, `byte[]`, `InputStream` — are
converted by Apache Camel to a `SAXSource` with external entities and external DTD loading disabled, and are
not affected. Probe 4 in the reproducer is that safe path, side by side with the unsafe one.
Because the factory is also registered as the **JAXP default** (the support extension ships
`META-INF/services/javax.xml.transform.TransformerFactory`), any other code in the application that obtains
a factory through `TransformerFactory.newInstance()` loses the same restrictions, without an error. That is
why the advisory lists extensions that never transform anything themselves:
| `camel-quarkus-xslt` | the `xslt` component path **and** the JAXP default |
| `camel-quarkus-xslt-saxon` | the JAXP default |
| `camel-quarkus-tika` | the JAXP default |
| `camel-quarkus-xmlsecurity` | the JAXP default |
| **Component** | `camel-quarkus-support-xalan` (XSLT support extension) |
| **CWE** | CWE-611 (Improper Restriction of XML External Entity Reference) |
| **Attack vector** | An external entity or external DTD declared in the XML document being transformed, where the body reaches the `xslt` endpoint already as a `javax.xml.transform.Source` |
| **Impact** | Read local files; issue requests to internal network locations (SSRF) |
| **Affected Versions** | From 3.2.0 before 3.33.3, from 3.34.0 before 3.40.0 |
| **Fixed Versions** | 3.33.3 (LTS stream), 3.40.0 |
| **GitHub issue** | [apache/camel-quarkus#9115]( |
| **Credit** | Discovered by internal analysis |
Advisory:
`XalanTransformerFactory` now applies the restrictions itself instead of relying on attributes Xalan cannot
- Documents being transformed are parsed with an `XMLReader` that resolves neither external general nor
external parameter entities and does not load external DTDs — the same configuration Apache Camel's
`XmlConverter.createSAXParserFactory()` uses for the bodies `camel-xslt` converts to a `SAXSource` itself.
A `SAXSource` carrying a caller-configured `XMLReader` is used as it is, and `DOMSource` and `StAXSource`
- Resources fetched at transform time by the `document()` function are denied unless the application's own
`URIResolver` resolves them, and the restriction is installed on every entry point that hands out something
to transform with, including the SAX push entry points whose transformers Xalan does not copy the factory
resolver onto. Applications that set their own resolver — `camel-xslt` does so on every exchange — keep
[`9a570b64`]( and
[`9dd11779`](
[`ad9c5236`]( and
[`3d886769`](
- Do not pass a `javax.xml.transform.Source` built from untrusted input into an `xslt` endpoint. Leave the
message body as `String`, `byte[]` or `InputStream` so that Apache Camel converts it to a `SAXSource` with
- `convertBodyTo` on an existing `Source` body is **not** a workaround: that conversion performs an identity
transform through the same factory. Probe 5 in the reproducer is that identity transform, and it leaks.
- Application or library code that relies on JAXP external access restrictions should request the JDK
implementation explicitly, naming `com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl`,
rather than relying on `TransformerFactory.newInstance()`. Probe 6 is that control, and it denies the read.
This repository is published for educational and defensive purposes: to help Apache Camel Quarkus users
understand the vulnerability, verify whether they are affected, and confirm that upgrading resolves it. Do
not use this material against systems you do not own or operate.
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
