{"repro_id":"REPRO-2026-00264","version":6,"title":"Apache Camel camel-docling improperly validates custom CLI arguments, enabling argument injection and path traversal when untrusted data is mapped into docling invocation headers.","repro_type":"security","status":"published","severity":"critical","cvss_score":9.1,"description":"Improper neutralization of argument delimiters in Apache Camel’s `camel-docling` component allows untrusted inputs to influence the `docling` CLI invocation. The component appended user-supplied arguments from `CamelDoclingCustomArguments` and path-bearing headers with weak validation (denylist of flags and literal `../` checks), enabling injection of unintended CLI flags and traversal sequences that resolve outside intended directories. The issue affects Apache Camel 4.15.0 through 4.18.2.","root_cause":"## Summary\nApache 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`.\n\n## Impact\n- **Package/component affected:** Maven package `org.apache.camel:camel-docling`, specifically `org.apache.camel.component.docling.DoclingProducer` handling of `CamelDoclingCustomArguments`.\n- **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.\n- **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.\n\n## Impact Parity\n- **Disclosed/claimed maximum impact:** `code_execution`.\n- **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.\n- **Parity:** `full` for the submitted `api_remote` / `code_execution` claim in the configured proof environment.\n- **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.\n\n## Root Cause\nAffected `camel-docling` versions appended the `CamelDoclingCustomArguments` header directly into the command list passed to `java.lang.ProcessBuilder`. The vulnerable flow is:\n\n1. A Camel route receives remote HTTP input, for example `GET /convert?args=...`.\n2. Route code maps that string into `CamelDoclingCustomArguments` as a `List<String>`.\n3. `DoclingProducer.addCustomArguments()` appends the list into the docling command.\n4. `DoclingProducer.executeDoclingCommand()` starts `new ProcessBuilder(command).start()`.\n5. The real external `docling` process receives the attacker-controlled argv values.\n\nIn 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.\n\nThe 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.\n\n## Reproduction Steps\n1. Run `bundle/repro/reproduction_steps.sh` from any directory. It is self-contained and uses `PRUVA_ROOT`/relative bundle paths.\n2. The script:\n   - Reuses the prepared project cache at `<project_cache_dir>/repo` when available.\n   - Builds a real Java Camel application with `camel-jetty` and `camel-docling`.\n   - Creates a real HTTP route `/convert` that maps the remote `args` query parameter into `CamelDoclingCustomArguments` and invokes `docling:convert`.\n   - Installs or reuses the real `docling` CLI in a Python virtual environment.\n   - 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.\n   - 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.\n3. Expected evidence:\n   - 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=<version> attempt=<n> uid=<uid>`.\n   - 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.\n\n## Evidence\nKey evidence from the successful consecutive runtime runs is under `bundle/logs/` and summarized in `bundle/repro/runtime_manifest.json`.\n\nImportant artifacts include:\n- `bundle/logs/reproduction_steps.log` — full run summary.\n- `bundle/logs/http_transcript.log` — HTTP requests and response previews for `/convert?args=...`.\n- `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.\n- `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.\n- `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.\n- `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.\n- `bundle/logs/fixed_4.18.3_attempt1_response.txt` and `bundle/logs/fixed_4.18.3_attempt2_response.txt` — fixed-version rejection evidence.\n- `bundle/repro/runtime_manifest.json` — structured runtime evidence manifest.\n- `bundle/repro/validation_verdict.json` — structured verdict.\n\nRepresentative excerpts from `bundle/logs/reproduction_steps.log`:\n\n```text\n[attempt] 4.16.0 vulnerable attempt=1 HTTP=500 docling_invoked=true rce_marker=true camel_rejected=false\n[attempt] 4.16.0 vulnerable attempt=2 HTTP=500 docling_invoked=true rce_marker=true camel_rejected=false\n[attempt] 4.18.2 vulnerable attempt=1 HTTP=500 docling_invoked=true rce_marker=true camel_rejected=false\n[attempt] 4.18.2 vulnerable attempt=2 HTTP=500 docling_invoked=true rce_marker=true camel_rejected=false\n[attempt] 4.18.3 fixed attempt=1 HTTP=500 docling_invoked=false rce_marker=false camel_rejected=true\n[attempt] 4.18.3 fixed attempt=2 HTTP=500 docling_invoked=false rce_marker=false camel_rejected=true\n```\n\nRepresentative marker evidence:\n\n```text\nPRUVA_RCE_CONFIRMED version=4.16.0 attempt=1 uid=1000\nPRUVA_RCE_CONFIRMED version=4.18.2 attempt=1 uid=1000\n```\n\nRepresentative fixed rejection:\n\n```text\nCustom argument '--pruva-rce-command-b64' is not a recognized docling CLI flag. Only known docling flags are permitted as custom arguments.\n```\n\nEnvironment 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`.\n\n## Recommendations / Next Steps\n- Upgrade Apache Camel to 4.18.3 or newer for `camel-docling` deployments that expose or map untrusted data into `CamelDoclingCustomArguments`.\n- Keep strict allowlisting for custom docling flags. Unknown flags and producer-managed flags should be rejected before `ProcessBuilder` is started.\n- Normalize and validate path-bearing argument values after decoding, and reject traversal components after normalization.\n- Treat docling plugin availability and external plugin loading as high-risk. Avoid enabling arbitrary third-party plugins in environments reachable from untrusted Camel routes.\n- Add regression tests covering:\n  - Unknown custom flags.\n  - Producer-managed flags such as output path controls.\n  - Extension/plugin-specific option names.\n  - Path traversal values.\n  - Shell metacharacter values, even though list-based `ProcessBuilder` does not invoke a shell.\n\n## Additional Notes\n- The reproduction script was run successfully multiple times, including two consecutive clean runs after the final classification patch.\n- 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.\n- 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.\n- 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.\n","ghsa_id":"GHSA-RPV3-6645-2VQC","cve_id":"CVE-2026-40047","cwe_id":"CWE-88 (Improper Neutralization of Argument Delimiters in a Command - Argument Injection)","source_url":"https://github.com/advisories/GHSA-RPV3-6645-2VQC","package":{"name":"apache/camel","ecosystem":"Maven","affected_versions":"4.15.0 through before 4.18.3","fixed_version":"4.18.3"},"reproduced_at":"2026-07-07T19:42:25.849982+00:00","duration_secs":2483.0,"tool_calls":497,"handoffs":3,"total_cost_usd":15.33652228,"agent_costs":{"judge":0.0627789,"repro":11.691140479999994,"support":0.0611169,"vuln_variant":3.5214860000000003},"cost_breakdown":{"judge":{"gpt-5.4-mini":0.0627789},"repro":{"accounts/fireworks/routers/glm-5p2-fast":6.798273479999998,"gpt-5.5":4.892866999999999},"support":{"accounts/fireworks/routers/glm-5p2-fast":0.0611169},"vuln_variant":{"gpt-5.5":3.5214860000000003}},"quality":{"confidence":"medium","idempotent_verified":false,"community_verifications":0},"environment":{"sandbox_image":"ghcr.io/n3mes1s/pruva-sandbox@sha256:8096b2518d6022e13d68f885c3b8ded6b4fe607098b1a1ccbfb99abc004d1dc1"},"published_at":"2026-07-07T19:43:12.194750+00:00","retracted":false,"artifacts":[{"path":"bundle/repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":23762,"category":"reproduction_script"},{"path":"bundle/repro/rca_report.md","filename":"rca_report.md","size":10106,"category":"analysis"},{"path":"bundle/vuln_variant/reproduction_steps.sh","filename":"reproduction_steps.sh","size":14807,"category":"reproduction_script"},{"path":"bundle/vuln_variant/rca_report.md","filename":"rca_report.md","size":10470,"category":"analysis"},{"path":"bundle/logs/vulnerable_4.16.0_docling_mock.log","filename":"vulnerable_4.16.0_docling_mock.log","size":1147,"category":"log"},{"path":"bundle/logs/vulnerable_4.18.2_docling_mock.log","filename":"vulnerable_4.18.2_docling_mock.log","size":517,"category":"log"},{"path":"bundle/logs/fixed_4.18.3_resp_unknownflag.txt","filename":"fixed_4.18.3_resp_unknownflag.txt","size":140,"category":"other"},{"path":"bundle/logs/real_4.16.0_resp_unknownflag.txt","filename":"real_4.16.0_resp_unknownflag.txt","size":1718,"category":"other"},{"path":"bundle/logs/real_4.16.0_resp_allowed.txt","filename":"real_4.16.0_resp_allowed.txt","size":47,"category":"other"},{"path":"bundle/logs/shellmeta_no_rce_evidence.txt","filename":"shellmeta_no_rce_evidence.txt","size":733,"category":"other"},{"path":"bundle/artifact_promotion_manifest.json","filename":"artifact_promotion_manifest.json","size":22378,"category":"other"},{"path":"bundle/artifact_promotion_report.json","filename":"artifact_promotion_report.json","size":22396,"category":"other"},{"path":"bundle/vuln_variant/source_identity.json","filename":"source_identity.json","size":1592,"category":"other"},{"path":"bundle/vuln_variant/root_cause_equivalence.json","filename":"root_cause_equivalence.json","size":1786,"category":"other"},{"path":"bundle/repro/validation_verdict.json","filename":"validation_verdict.json","size":1292,"category":"other"},{"path":"bundle/repro/runtime_manifest.json","filename":"runtime_manifest.json","size":2530,"category":"other"},{"path":"bundle/logs/reproduction_steps.log","filename":"reproduction_steps.log","size":4687,"category":"log"},{"path":"bundle/logs/http_transcript.log","filename":"http_transcript.log","size":5578,"category":"log"},{"path":"bundle/logs/vulnerable_4.16.0_attempt1_rce_marker.txt","filename":"vulnerable_4.16.0_attempt1_rce_marker.txt","size":54,"category":"other"},{"path":"bundle/logs/vulnerable_4.18.2_attempt1_rce_marker.txt","filename":"vulnerable_4.18.2_attempt1_rce_marker.txt","size":54,"category":"other"},{"path":"bundle/logs/fixed_4.18.3_attempt1_response.txt","filename":"fixed_4.18.3_attempt1_response.txt","size":139,"category":"other"},{"path":"bundle/logs/maven_compile.log","filename":"maven_compile.log","size":0,"category":"log"},{"path":"bundle/logs/docling_plugin_install.log","filename":"docling_plugin_install.log","size":556,"category":"log"},{"path":"bundle/logs/vulnerable_4.16.0_attempt1_service.log","filename":"vulnerable_4.16.0_attempt1_service.log","size":219,"category":"log"},{"path":"bundle/logs/vulnerable_4.16.0_attempt1_docling_invocations.log","filename":"vulnerable_4.16.0_attempt1_docling_invocations.log","size":641,"category":"log"},{"path":"bundle/logs/vulnerable_4.16.0_attempt1_response.txt","filename":"vulnerable_4.16.0_attempt1_response.txt","size":1827,"category":"other"},{"path":"bundle/logs/vulnerable_4.16.0_attempt2_service.log","filename":"vulnerable_4.16.0_attempt2_service.log","size":219,"category":"log"},{"path":"bundle/logs/vulnerable_4.16.0_attempt2_docling_invocations.log","filename":"vulnerable_4.16.0_attempt2_docling_invocations.log","size":640,"category":"log"},{"path":"bundle/logs/vulnerable_4.16.0_attempt2_response.txt","filename":"vulnerable_4.16.0_attempt2_response.txt","size":1827,"category":"other"},{"path":"bundle/logs/vulnerable_4.16.0_attempt2_rce_marker.txt","filename":"vulnerable_4.16.0_attempt2_rce_marker.txt","size":54,"category":"other"},{"path":"bundle/logs/vulnerable_4.18.2_attempt1_service.log","filename":"vulnerable_4.18.2_attempt1_service.log","size":219,"category":"log"},{"path":"bundle/logs/vulnerable_4.18.2_attempt1_docling_invocations.log","filename":"vulnerable_4.18.2_attempt1_docling_invocations.log","size":641,"category":"log"},{"path":"bundle/logs/vulnerable_4.18.2_attempt1_response.txt","filename":"vulnerable_4.18.2_attempt1_response.txt","size":1827,"category":"other"},{"path":"bundle/logs/vulnerable_4.18.2_attempt2_service.log","filename":"vulnerable_4.18.2_attempt2_service.log","size":219,"category":"log"},{"path":"bundle/logs/vulnerable_4.18.2_attempt2_docling_invocations.log","filename":"vulnerable_4.18.2_attempt2_docling_invocations.log","size":641,"category":"log"},{"path":"bundle/logs/vulnerable_4.18.2_attempt2_response.txt","filename":"vulnerable_4.18.2_attempt2_response.txt","size":1827,"category":"other"},{"path":"bundle/logs/vulnerable_4.18.2_attempt2_rce_marker.txt","filename":"vulnerable_4.18.2_attempt2_rce_marker.txt","size":54,"category":"other"},{"path":"bundle/logs/fixed_4.18.3_attempt1_service.log","filename":"fixed_4.18.3_attempt1_service.log","size":219,"category":"log"},{"path":"bundle/logs/fixed_4.18.3_attempt2_service.log","filename":"fixed_4.18.3_attempt2_service.log","size":219,"category":"log"},{"path":"bundle/logs/fixed_4.18.3_attempt2_response.txt","filename":"fixed_4.18.3_attempt2_response.txt","size":139,"category":"other"},{"path":"bundle/vuln_variant/patch_analysis.md","filename":"patch_analysis.md","size":7078,"category":"documentation"},{"path":"bundle/vuln_variant/variant_manifest.json","filename":"variant_manifest.json","size":3685,"category":"other"},{"path":"bundle/vuln_variant/validation_verdict.json","filename":"validation_verdict.json","size":3283,"category":"other"},{"path":"bundle/vuln_variant/runtime_manifest.json","filename":"runtime_manifest.json","size":1912,"category":"other"},{"path":"bundle/logs/vuln_variant/reproduction_steps.log","filename":"reproduction_steps.log","size":1322,"category":"log"},{"path":"bundle/logs/vuln_variant/fixed_4.18.3_docling_invocations.log","filename":"fixed_4.18.3_docling_invocations.log","size":998,"category":"log"},{"path":"bundle/logs/vuln_variant/fixed_version.txt","filename":"fixed_version.txt","size":505,"category":"other"},{"path":"bundle/logs/vuln_variant/vulnerable_4.18.2_service.log","filename":"vulnerable_4.18.2_service.log","size":347,"category":"log"},{"path":"bundle/logs/vuln_variant/vulnerable_4.18.2_docling_invocations.log","filename":"vulnerable_4.18.2_docling_invocations.log","size":1023,"category":"log"},{"path":"bundle/logs/vuln_variant/vulnerable_4.18.2_response.txt","filename":"vulnerable_4.18.2_response.txt","size":96,"category":"other"},{"path":"bundle/logs/vuln_variant/vulnerable_4.18.2_escaped_marker.txt","filename":"vulnerable_4.18.2_escaped_marker.txt","size":412,"category":"other"},{"path":"bundle/logs/vuln_variant/fixed_4.18.3_service.log","filename":"fixed_4.18.3_service.log","size":342,"category":"log"},{"path":"bundle/logs/vuln_variant/fixed_4.18.3_response.txt","filename":"fixed_4.18.3_response.txt","size":96,"category":"other"},{"path":"bundle/logs/vuln_variant/fixed_4.18.3_escaped_marker.txt","filename":"fixed_4.18.3_escaped_marker.txt","size":402,"category":"other"},{"path":"bundle/logs/vuln_variant/fixed_4.18.3_custom_args_control_response.txt","filename":"fixed_4.18.3_custom_args_control_response.txt","size":172,"category":"other"},{"path":"bundle/logs/vuln_variant/latest_4.21.0_service.log","filename":"latest_4.21.0_service.log","size":343,"category":"log"},{"path":"bundle/logs/vuln_variant/latest_4.21.0_docling_invocations.log","filename":"latest_4.21.0_docling_invocations.log","size":1003,"category":"log"},{"path":"bundle/logs/vuln_variant/latest_4.21.0_response.txt","filename":"latest_4.21.0_response.txt","size":127,"category":"other"},{"path":"bundle/logs/vuln_variant/latest_4.21.0_escaped_marker.txt","filename":"latest_4.21.0_escaped_marker.txt","size":404,"category":"other"},{"path":"bundle/logs/vuln_variant/latest_4.21.0_custom_args_control_response.txt","filename":"latest_4.21.0_custom_args_control_response.txt","size":172,"category":"other"}]}