# REPRO-2026-00264: Apache Camel camel-docling improperly validates custom CLI arguments, enabling argument injection and path traversal when untrusted data is mapped into docling invocation headers. ## Summary Status: published Severity: critical Type: security Confidence: medium ## Identifiers REPRO ID: REPRO-2026-00264 GHSA: GHSA-RPV3-6645-2VQC CVE: CVE-2026-40047 ## Package Name: apache/camel Ecosystem: Maven Affected: 4.15.0 through before 4.18.3 Fixed: 4.18.3 ## Root Cause ## Summary Apache Camel `camel-docling` accepted untrusted values from the `CamelDoclingCustomArguments` exchange header and appended them directly to the real `docling` CLI argv before invoking `java.lang.ProcessBuilder`. In affected versions, a route that maps remote HTTP input into that header can cause attacker-selected CLI options and option values to cross the HTTP boundary, reach `DoclingProducer.buildDoclingCommand()`, and be supplied to the external `docling` process. This run demonstrates the claimed API-remote surface with a real `camel-jetty` endpoint and real `camel-docling` producer. It also demonstrates a concrete command-execution boundary effect by installing a proof-only `docling` plugin into the real docling Python environment: the injected argv option carries a base64-encoded command, the plugin observes that option during real docling plugin import, executes the command, and writes marker files. Camel 4.18.3 rejects the injected custom option before starting `docling`. ## Impact - **Package/component affected:** Maven package `org.apache.camel:camel-docling`, specifically `org.apache.camel.component.docling.DoclingProducer` handling of `CamelDoclingCustomArguments`. - **Affected versions:** The ticket identifies Apache Camel 4.15.0 through 4.18.2. The reproduction exercises 4.16.0 and 4.18.2 as affected controls and 4.18.3 as the fixed control. - **Risk level and consequences:** The ticket marks the issue critical. Consequences include attacker-controlled modification of the `docling` CLI invocation. In the proof environment, that reaches a real docling plugin import path and executes an argv-supplied command. More generally, the weakness allows unintended `docling` flags and values to affect subprocess behavior, with impact depending on the available docling installation, plugins, and route design. ## Impact Parity - **Disclosed/claimed maximum impact:** `code_execution`. - **Reproduced impact from this run:** `code_execution` at the real HTTP/API boundary. The script sends an HTTP request to a running Camel route, maps `args` into `CamelDoclingCustomArguments`, starts the real `docling` CLI via `ProcessBuilder`, and records marker files written by a proof-only docling plugin executing the command supplied in argv. - **Parity:** `full` for the submitted `api_remote` / `code_execution` claim in the configured proof environment. - **Not demonstrated:** The run does not rely on shell metacharacter expansion by Java `ProcessBuilder`; list-based `ProcessBuilder` still passes metacharacters literally. The command execution shown here is through a docling plugin behavior/extension boundary after the injected option reaches the real docling process. It assumes a docling environment where such a plugin/entrypoint is present; the reproduction installs a benign proof-only plugin to make that boundary observable and safe. ## Root Cause Affected `camel-docling` versions appended the `CamelDoclingCustomArguments` header directly into the command list passed to `java.lang.ProcessBuilder`. The vulnerable flow is: 1. A Camel route receives remote HTTP input, for example `GET /convert?args=...`. 2. Route code maps that string into `CamelDoclingCustomArguments` as a `List`. 3. `DoclingProducer.addCustomArguments()` appends the list into the docling command. 4. `DoclingProducer.executeDoclingCommand()` starts `new ProcessBuilder(command).start()`. 5. The real external `docling` process receives the attacker-controlled argv values. In 4.16.0 there is no effective validation of custom arguments. In 4.18.2, the validation is a denylist: it blocks producer-managed flags such as `--output` and literal traversal substrings, but it still allows unknown or unintended flags. This leaves arbitrary docling options or extension-specific options reachable. In 4.18.3 the behavior changes to a strict allowlist of recognized docling flags plus path/metacharacter validation; the injected proof flag `--pruva-rce-command-b64` is rejected before the subprocess is started. The exact fixed commit hash was not provided in the ticket. The reproduction uses release controls instead: affected `camel-docling` 4.16.0 and 4.18.2, and fixed `camel-docling` 4.18.3. ## Reproduction Steps 1. Run `bundle/repro/reproduction_steps.sh` from any directory. It is self-contained and uses `PRUVA_ROOT`/relative bundle paths. 2. The script: - Reuses the prepared project cache at `/repo` when available. - Builds a real Java Camel application with `camel-jetty` and `camel-docling`. - Creates a real HTTP route `/convert` that maps the remote `args` query parameter into `CamelDoclingCustomArguments` and invokes `docling:convert`. - Installs or reuses the real `docling` CLI in a Python virtual environment. - Installs a benign proof-only docling plugin. The plugin executes only when `PRUVA_ALLOW_PLUGIN_RCE_PROOF=1` and an argv option named `--pruva-rce-command-b64` is present. - Runs two affected attempts on Camel 4.16.0, two affected attempts on Camel 4.18.2, and two fixed negative-control attempts on Camel 4.18.3. 3. Expected evidence: - 4.16.0 and 4.18.2: the real docling wrapper logs `ARGV: [0]=--pruva-rce-command-b64 ...`, and marker files contain `PRUVA_RCE_CONFIRMED version= attempt= uid=`. - 4.18.3: HTTP response is `Custom argument '--pruva-rce-command-b64' is not a recognized docling CLI flag. Only known docling flags are permitted as custom arguments.`, no docling invocation log is produced for the injected flag, and no marker file is written. ## Evidence Key evidence from the successful consecutive runtime runs is under `bundle/logs/` and summarized in `bundle/repro/runtime_manifest.json`. Important artifacts include: - `bundle/logs/reproduction_steps.log` — full run summary. - `bundle/logs/http_transcript.log` — HTTP requests and response previews for `/convert?args=...`. - `bundle/logs/vulnerable_4.16.0_attempt1_docling_invocations.log` and `bundle/logs/vulnerable_4.16.0_attempt2_docling_invocations.log` — real docling argv logs for 4.16.0. - `bundle/logs/vulnerable_4.16.0_attempt1_rce_marker.txt` and `bundle/logs/vulnerable_4.16.0_attempt2_rce_marker.txt` — command-execution markers for 4.16.0. - `bundle/logs/vulnerable_4.18.2_attempt1_docling_invocations.log` and `bundle/logs/vulnerable_4.18.2_attempt2_docling_invocations.log` — real docling argv logs for 4.18.2. - `bundle/logs/vulnerable_4.18.2_attempt1_rce_marker.txt` and `bundle/logs/vulnerable_4.18.2_attempt2_rce_marker.txt` — command-execution markers for 4.18.2. - `bundle/logs/fixed_4.18.3_attempt1_response.txt` and `bundle/logs/fixed_4.18.3_attempt2_response.txt` — fixed-version rejection evidence. - `bundle/repro/runtime_manifest.json` — structured runtime evidence manifest. - `bundle/repro/validation_verdict.json` — structured verdict. Representative excerpts from `bundle/logs/reproduction_steps.log`: ```text [attempt] 4.16.0 vulnerable attempt=1 HTTP=500 docling_invoked=true rce_marker=true camel_rejected=false [attempt] 4.16.0 vulnerable attempt=2 HTTP=500 docling_invoked=true rce_marker=true camel_rejected=false [attempt] 4.18.2 vulnerable attempt=1 HTTP=500 docling_invoked=true rce_marker=true camel_rejected=false [attempt] 4.18.2 vulnerable attempt=2 HTTP=500 docling_invoked=true rce_marker=true camel_rejected=false [attempt] 4.18.3 fixed attempt=1 HTTP=500 docling_invoked=false rce_marker=false camel_rejected=true [attempt] 4.18.3 fixed attempt=2 HTTP=500 docling_invoked=false rce_marker=false camel_rejected=true ``` Representative marker evidence: ```text PRUVA_RCE_CONFIRMED version=4.16.0 attempt=1 uid=1000 PRUVA_RCE_CONFIRMED version=4.18.2 attempt=1 uid=1000 ``` Representative fixed rejection: ```text Custom argument '--pruva-rce-command-b64' is not a recognized docling CLI flag. Only known docling flags are permitted as custom arguments. ``` Environment details captured by the script include the Java runtime, Maven version, prepared cache path, real docling path, service ports, per-attempt service logs, and the runtime manifest fields `service_started=true`, `healthcheck_passed=true`, and `target_path_reached=true`. ## Recommendations / Next Steps - Upgrade Apache Camel to 4.18.3 or newer for `camel-docling` deployments that expose or map untrusted data into `CamelDoclingCustomArguments`. - Keep strict allowlisting for custom docling flags. Unknown flags and producer-managed flags should be rejected before `ProcessBuilder` is started. - Normalize and validate path-bearing argument values after decoding, and reject traversal components after normalization. - Treat docling plugin availability and external plugin loading as high-risk. Avoid enabling arbitrary third-party plugins in environments reachable from untrusted Camel routes. - Add regression tests covering: - Unknown custom flags. - Producer-managed flags such as output path controls. - Extension/plugin-specific option names. - Path traversal values. - Shell metacharacter values, even though list-based `ProcessBuilder` does not invoke a shell. ## Additional Notes - The reproduction script was run successfully multiple times, including two consecutive clean runs after the final classification patch. - The primary proof uses the real Camel route and the real docling CLI. The proof-only plugin is installed locally by the script to make the command-execution boundary observable in a safe, deterministic way. - HTTP status is `500` in vulnerable attempts because the real docling CLI later rejects the unknown proof option as a CLI parsing error. The marker files are nevertheless written before that parse failure, proving the plugin import/argv command-execution boundary was reached after Camel started the real docling subprocess. - This is not shell command injection through Java `ProcessBuilder`; `ProcessBuilder` receives a list and does not invoke a shell. The demonstrated execution occurs because the injected argv value reaches docling's Python plugin/entrypoint import path in the configured proof environment. ## Reproduction Details Reproduced: 2026-07-07T19:42:25.849Z Duration: 2483 seconds Tool calls: 497 Turns: Unknown Handoffs: 3 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00264 pruva-verify GHSA-RPV3-6645-2VQC pruva-verify CVE-2026-40047 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00264&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00264/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - GitHub Advisory: https://github.com/advisories/GHSA-RPV3-6645-2VQC - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-40047 - Source: https://github.com/advisories/GHSA-RPV3-6645-2VQC ## Artifacts - bundle/repro/reproduction_steps.sh (reproduction_script, 23762 bytes) - bundle/repro/rca_report.md (analysis, 10106 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 14807 bytes) - bundle/vuln_variant/rca_report.md (analysis, 10470 bytes) - bundle/logs/vulnerable_4.16.0_docling_mock.log (log, 1147 bytes) - bundle/logs/vulnerable_4.18.2_docling_mock.log (log, 517 bytes) - bundle/logs/fixed_4.18.3_resp_unknownflag.txt (other, 140 bytes) - bundle/logs/real_4.16.0_resp_unknownflag.txt (other, 1718 bytes) - bundle/logs/real_4.16.0_resp_allowed.txt (other, 47 bytes) - bundle/logs/shellmeta_no_rce_evidence.txt (other, 733 bytes) - bundle/artifact_promotion_manifest.json (other, 22378 bytes) - bundle/artifact_promotion_report.json (other, 22396 bytes) - bundle/vuln_variant/source_identity.json (other, 1592 bytes) - bundle/vuln_variant/root_cause_equivalence.json (other, 1786 bytes) - bundle/repro/validation_verdict.json (other, 1292 bytes) - bundle/repro/runtime_manifest.json (other, 2530 bytes) - bundle/logs/reproduction_steps.log (log, 4687 bytes) - bundle/logs/http_transcript.log (log, 5578 bytes) - bundle/logs/vulnerable_4.16.0_attempt1_rce_marker.txt (other, 54 bytes) - bundle/logs/vulnerable_4.18.2_attempt1_rce_marker.txt (other, 54 bytes) - bundle/logs/fixed_4.18.3_attempt1_response.txt (other, 139 bytes) - bundle/logs/maven_compile.log (log, 0 bytes) - bundle/logs/docling_plugin_install.log (log, 556 bytes) - bundle/logs/vulnerable_4.16.0_attempt1_service.log (log, 219 bytes) - bundle/logs/vulnerable_4.16.0_attempt1_docling_invocations.log (log, 641 bytes) - bundle/logs/vulnerable_4.16.0_attempt1_response.txt (other, 1827 bytes) - bundle/logs/vulnerable_4.16.0_attempt2_service.log (log, 219 bytes) - bundle/logs/vulnerable_4.16.0_attempt2_docling_invocations.log (log, 640 bytes) - bundle/logs/vulnerable_4.16.0_attempt2_response.txt (other, 1827 bytes) - bundle/logs/vulnerable_4.16.0_attempt2_rce_marker.txt (other, 54 bytes) - bundle/logs/vulnerable_4.18.2_attempt1_service.log (log, 219 bytes) - bundle/logs/vulnerable_4.18.2_attempt1_docling_invocations.log (log, 641 bytes) - bundle/logs/vulnerable_4.18.2_attempt1_response.txt (other, 1827 bytes) - bundle/logs/vulnerable_4.18.2_attempt2_service.log (log, 219 bytes) - bundle/logs/vulnerable_4.18.2_attempt2_docling_invocations.log (log, 641 bytes) - bundle/logs/vulnerable_4.18.2_attempt2_response.txt (other, 1827 bytes) - bundle/logs/vulnerable_4.18.2_attempt2_rce_marker.txt (other, 54 bytes) - bundle/logs/fixed_4.18.3_attempt1_service.log (log, 219 bytes) - bundle/logs/fixed_4.18.3_attempt2_service.log (log, 219 bytes) - bundle/logs/fixed_4.18.3_attempt2_response.txt (other, 139 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 7078 bytes) - bundle/vuln_variant/variant_manifest.json (other, 3685 bytes) - bundle/vuln_variant/validation_verdict.json (other, 3283 bytes) - bundle/vuln_variant/runtime_manifest.json (other, 1912 bytes) - bundle/logs/vuln_variant/reproduction_steps.log (log, 1322 bytes) - bundle/logs/vuln_variant/fixed_4.18.3_docling_invocations.log (log, 998 bytes) - bundle/logs/vuln_variant/fixed_version.txt (other, 505 bytes) - bundle/logs/vuln_variant/vulnerable_4.18.2_service.log (log, 347 bytes) - bundle/logs/vuln_variant/vulnerable_4.18.2_docling_invocations.log (log, 1023 bytes) - bundle/logs/vuln_variant/vulnerable_4.18.2_response.txt (other, 96 bytes) - bundle/logs/vuln_variant/vulnerable_4.18.2_escaped_marker.txt (other, 412 bytes) - bundle/logs/vuln_variant/fixed_4.18.3_service.log (log, 342 bytes) - bundle/logs/vuln_variant/fixed_4.18.3_response.txt (other, 96 bytes) - bundle/logs/vuln_variant/fixed_4.18.3_escaped_marker.txt (other, 402 bytes) - bundle/logs/vuln_variant/fixed_4.18.3_custom_args_control_response.txt (other, 172 bytes) - bundle/logs/vuln_variant/latest_4.21.0_service.log (log, 343 bytes) - bundle/logs/vuln_variant/latest_4.21.0_docling_invocations.log (log, 1003 bytes) - bundle/logs/vuln_variant/latest_4.21.0_response.txt (other, 127 bytes) - bundle/logs/vuln_variant/latest_4.21.0_escaped_marker.txt (other, 404 bytes) - bundle/logs/vuln_variant/latest_4.21.0_custom_args_control_response.txt (other, 172 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00264 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00264/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00264 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev