
Runnable Spring Boot and Quarkus reproducer for CVE-2026-91858, an XXE in Apache Camel camel-fop via a third-party TransformerFactory, with affected and patched verification.
Runnable proof-of-concept reproducer for the Apache Camel vulnerability where camel-fop's FopProducer created its
javax.xml.transform.TransformerFactory with only FEATURE_SECURE_PROCESSING before running an identity transform
over the XSL-FO message body. On the JDK's built-in transformer that feature alone already empties the
accessExternalDTD/accessExternalStylesheet limits, so external resources are refused. But when a third-party
JAXP TransformerFactory such as Saxon-HE is on the classpath — e.g. because the application also uses
camel-xslt-saxon or camel-saxon — TransformerFactory.newInstance() returns that implementation, and
FEATURE_SECURE_PROCESSING alone does not restrict external resource access on it. A DOCTYPE with an external
subset declared in the XSL-FO body is then resolved while the body is transformed, so an external sender's document can
read files readable by the Camel process or make it issue requests to hosts it names (CWE-611).
| Runtime | Directory | Stack |
|---|---|---|
| Camel Spring Boot | camel-spring-boot/ | Spring Boot 3.5.13 + camel-fop 4.22.0 (affected; fixed 4.22.1) + Saxon-HE 13.0 |
| Camel Quarkus | camel-quarkus/ | Quarkus 3.39.0 + camel-quarkus-fop (Camel 4.22.0, affected; fixed control CQ 3.40.0 / Camel 4.22.1) + Saxon-HE 13.0 |
Both modules demonstrate the affected and the fixed behaviour against the real FopProducer. Saxon-HE is on the
classpath in both — it is the precondition for the flaw; a default deployment using the JDK transformer is not
affected before or after the fix.
The verifier endpoint /exploit/attack starts an in-process HTTP callback and sends an XSL-FO document to fop:pdf
whose DOCTYPE declares an external DTD pointing at that callback:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE fo:root SYSTEM "http://localhost:<port>/external.dtd">
<fo:root …>…Hello…</fo:root>
The oracle is simply whether the callback was requested — a definitive proof that the external resource was fetched:
TransformerFactory.newInstance() returns Saxon, which under secure processing alone still fetches the
external DTD — the callback is hit (the same mechanism reads file:// URLs).ACCESS_EXTERNAL_DTD is set to "", so Saxon refuses the external DTD; the callback is never hit.# Camel Spring Boot
cd camel-spring-boot
mvn clean package
docker compose up -d --build
curl -s http://localhost:8080/exploit/attack # affected: callback hit = true; >>> PROVEN
docker compose down
# Patched control
mvn clean package -Dcamel.version=4.22.1
java -Djava.awt.headless=true -jar target/cve-2026-91858-fop-xxe-0.0.1-SNAPSHOT.jar &
curl -s http://localhost:8080/exploit/attack # fixed: callback hit = false; >>> Not exploited
# Camel Quarkus
cd camel-quarkus
mvn clean package
docker compose up -d --build
curl -s http://localhost:8080/exploit/attack # affected (Camel 4.22.0): callback hit = true
docker compose down
# Patched control: CQ 3.40.0 ships Camel 4.22.1
mvn clean package -Dcamel-quarkus.version=3.40.0 -Dquarkus.platform.version=3.40.0
java -Djava.awt.headless=true -jar target/quarkus-app/quarkus-run.jar &
curl -s http://localhost:8080/exploit/attack # fixed: callback hit = false
| Property | Value |
|---|---|
| Component | camel-fop (FopProducer) |
| CWE | CWE-611 (Improper Restriction of XML External Entity Reference) |
| Severity | MEDIUM |
| Attack vector | An external sender's XSL-FO reaches a fop: producer, on a deployment that also has a third-party JAXP TransformerFactory (e.g. Saxon-HE) on the classpath |
| Impact | Read local files readable by the Camel process; SSRF to internal hosts the document names |
| Affected Versions | From 4.0.0 before 4.18.5, from 4.19.0 before 4.22.1 (only with a non-JDK TransformerFactory present) |
| Fixed Versions | 4.18.5, 4.22.1, 4.23.0 |
| JIRA | CAMEL-24605 |
| Credit | Claude Security Scan Tool |
Advisory: https://camel.apache.org/security/CVE-2026-91858.html
The Quarkus affected module is built on CQ 3.39.0 (Camel 4.22.0). The patched control is CQ 3.40.0 (Camel 4.22.1). Saxon-HE 13.0 is added to both modules as the stand-in for an application that also uses camel-xslt-saxon/camel-saxon; without a non-JDK TransformerFactory the flaw is not reachable.
FopProducer now also sets XMLConstants.ACCESS_EXTERNAL_DTD and
XMLConstants.ACCESS_EXTERNAL_STYLESHEET to the empty string on the TransformerFactory it creates (in addition to
FEATURE_SECURE_PROCESSING), matching the secure XML processing configuration used by XmlConverter. External DTD and
stylesheet access is therefore refused on any JAXP implementation that honours those attributes, including Saxon-HE.
Do not send untrusted XSL-FO to a fop: producer, or remove the alternative JAXP TransformerFactory (such as
Saxon-HE) from the classpath so the JDK transformer — which already refuses external DTDs under secure processing — is
used.
This repository is published for educational and defensive purposes: to help Apache Camel users understand the vulnerability, verify whether they are affected, and confirm that upgrading resolves it. The external DTD points at an in-process callback on localhost; nothing outside the reproducer is contacted and no sensitive file is read. Do not use this material against systems you do not own or operate.