
Runnable proof-of-concept reproducer for CVE-2026-85312, demonstrating Apache Camel saga hijacking via the Long-Running-Action header in Spring Boot and Quarkus.
Long-Running-Action headerRunnable proof-of-concept reproducer for the Apache Camel vulnerability where camel-core's SagaProcessor selected the
saga coordinator from the Long-Running-Action message header on every exchange. That header name carries no
Camel prefix, so it sits outside the namespace that consumer header filters strip — an external sender could supply
it on an inbound message and thereby name the saga its exchange joins, attaching their request to a long-running
action started by someone else (or associating compensation with an action they did not initiate). The exposure is
bounded to deployments running sagas, but where it applies the sender controls which saga an exchange belongs to
(CWE-20).
| Runtime | Directory | Stack |
|---|---|---|
| Camel Spring Boot | camel-spring-boot/ | Spring Boot 3.5.13 + camel-spring-boot 4.22.0 (affected; fixed 4.22.1) |
| Camel Quarkus | camel-quarkus/ | Quarkus 3.39.0 + camel-quarkus-saga (Camel 4.22.0, affected; fixed control CQ 3.40.0 / Camel 4.22.1) |
Both modules demonstrate the affected and the fixed behaviour against the real camel-core SagaProcessor.
The driver mirrors the fix's own regression test (SagaHeaderCannotSelectCoordinatorTest). It builds a saga route
from("direct:…").saga().propagation(MANDATORY) backed by an in-memory saga service that records every id it is
asked to resolve, then sends one exchange carrying the inbound header Long-Running-Action = a-saga-id-from-the-wire
— the id an external sender would place on the wire. The oracle is simply which saga ids reach the service:
a-saga-id-from-the-wire is looked up
— the message chose the coordinator.In both builds the MANDATORY exchange ultimately fails (there is no real saga to join) — the vulnerability is not the
failure but that an attacker-chosen id was used to select the coordinator at all, which is why the oracle watches the
lookup rather than the outcome.
# Camel Spring Boot
cd camel-spring-boot
mvn clean package
docker compose up -d --build
curl -s http://localhost:8080/exploit/attack # affected: wire id looked up = true; >>> PROVEN
docker compose down
# Patched control
mvn clean package -Dcamel.version=4.22.1
java -jar target/cve-2026-85312-saga-header-hijack-0.0.1-SNAPSHOT.jar &
curl -s http://localhost:8080/exploit/attack # fixed: wire id looked up = 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): wire id looked up = 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 -jar target/quarkus-app/quarkus-run.jar &
curl -s http://localhost:8080/exploit/attack # fixed: wire id looked up = false
| Property | Value |
|---|---|
| Component | camel-core (SagaProcessor.getCurrentSagaCoordinator) |
| CWE | CWE-20 (Improper Input Validation) |
| Severity | MEDIUM |
| Attack vector | An external sender supplies the unprefixed Long-Running-Action header on an inbound message to a route running the saga EIP |
| Impact | The sender names the saga an exchange joins — attaching to another party's long-running action, or misassociating compensation |
| Affected Versions | From 4.0.0 before 4.18.5, from 4.19.0 before 4.22.1 |
| Fixed Versions | 4.18.5, 4.22.1, 4.23.0 |
| JIRA | CAMEL-24449 |
| Credit | Claude Security Scan Tool |
Advisory: https://camel.apache.org/security/CVE-2026-85312.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).
SagaProcessor now consults the Long-Running-Action header only when
the saga service advertises that it participates in such a protocol, through the new
CamelSagaService.isLongRunningActionHeaderSupported() (default false; the LRA service overrides it to true). The
internal exchange state — which survives removeHeaders("*") — remains the normal carrier of the saga id, so a message
can no longer pick the coordinator for a service that runs no external coordinator.
Strip the header at the trust boundary with removeHeaders("Long-Running-Action") on every consumer that accepts
messages from an untrusted sender.
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 demonstration runs a saga route in-process with an in-memory saga service; the "attacker" saga id is a benign placeholder string. Do not use this material against systems you do not own or operate.