# REPRO-2026-00236: Orkes Conductor unauthenticated RCE via inline script evaluators ## Summary Status: published Severity: critical Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00236 CVE: CVE-2026-58138 ## Package Name: Unknown Ecosystem: Unknown Affected: Unknown Fixed: Unknown ## Root Cause # RCA Report — CVE-2026-58138 ## Summary CVE-2026-58138 is an unauthenticated remote code execution vulnerability in Orkes/Conductor-OSS (the community workflow orchestration engine) versions **3.21.21 through before 3.30.2**. The `INLINE` system task (and the related `LAMBDA`, `DO_WHILE`, and `SWITCH` task types) evaluates a user-supplied JavaScript expression inside a GraalVM polyglot `Context` that is built with **full host access** (`HostAccess.ALL`) and, for the Python evaluator, `allowAllAccess(true)`. Because the community Conductor REST API performs **no authentication by default**, any remote attacker who can reach the API can register a workflow definition whose `INLINE` task carries a malicious `expression`, start the workflow, and have the Conductor JVM execute arbitrary operating-system commands. This was reproduced end-to-end against the real `conductoross/conductor:3.22.3` Docker image: an unauthenticated `POST /api/metadata/workflow` + `POST /api/workflow/{name}` caused `Runtime.exec(["sh","-c",CMD])` to run **as root** inside the Conductor host, and the command's stdout was returned verbatim in the task's `outputData.result`. ## Impact - **Package/component affected:** `conductor-oss/conductor` — `core/src/main/java/com/netflix/conductor/core/events/ScriptEvaluator.java` (the GraalVM `js` evaluator context) and `core/src/main/java/com/netflix/conductor/core/execution/evaluators/PythonEvaluator.java` (the `python` evaluator context). Reachable through the `INLINE` workflow system task (`com.netflix.conductor.core.execution.tasks.Inline`) and `LAMBDA`/`DO_WHILE`/`SWITCH` which also evaluate script expressions. - **Affected versions:** 3.21.21 … < 3.30.2 (confirmed on 3.22.3). The plain `allowHostAccess(HostAccess.ALL)` configuration is present through ~3.29.x; 3.30.0/3.30.1 added a partial `denyAccess(...)` blocklist (reflection blocked but class loading still permitted); the complete fix lands in **3.30.2**. - **Risk level and consequences:** **Critical** (CVSS 9.8, `AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H`). Full unauthenticated remote compromise of the orchestrator host: arbitrary command execution as the Conductor process user (root in the default Docker image), exposing the engine's persistence/queues, stored credentials, and every system its workflows touch. ## Impact Parity - **Disclosed/claimed maximum impact:** Unauthenticated remote code execution (arbitrary OS command execution) — `code_execution`. - **Reproduced impact from this run:** Unauthenticated remote code execution as **root** — the attacker-supplied shell command (`id; echo ; hostname; cat /etc/os-release`) executed inside the Conductor host and its stdout (`uid=0(root)…`, the unique marker, the hostname, and the OS release line) was returned through the unauthenticated API. A second, independent proof confirmed a marker file was written to the container filesystem. - **Parity:** `full`. The proof exercises the exact claimed surface (unauthenticated remote API → inline script → OS command execution) and demonstrates the claimed maximum impact (code execution as root). - **Not demonstrated:** Nothing — the reproduction reached the full claimed impact, not merely a crash or memory-safety symptom. ## Root Cause Conductor's `INLINE` task evaluates user-supplied script through `ScriptEvaluator.eval()`, which builds a GraalVM polyglot `Context` for the `js` language. In the vulnerable versions the context is created with **no sandbox**: ```java // core/.../events/ScriptEvaluator.java (v3.22.3, createNewContext()) private static Context createNewContext() { return Context.newBuilder("js") .allowHostAccess(HostAccess.ALL) // <-- every public Java method/field callable .option("engine.WarnInterpreterOnly", "false") .build(); } ``` `HostAccess.ALL` permits the script to call any public method or access any public field on every Java host object it can reach. The `INLINE` task binds its `inputParameters` map as the JavaScript global `$` — a **live Java object** (`jsBindings.putMember("$", input)` in `ScriptEvaluator.eval()`). From `$` the script bootstraps Java reflection without needing any `Java.type`/class-lookup permission, because everything is reached by calling public methods on host objects that `HostAccess.ALL` already exposes: ```js var k = $.getClass().getClass(); // java.lang.Class var S = k.getMethod('getName').getReturnType(); // java.lang.String class var fN = k.getMethod('forName', S); // Class.forName(String) var RT = fN.invoke(null, ['java.lang.Runtime']); // java.lang.Runtime var rt = RT.getMethod('getRuntime').invoke(null, []); // build String[]{'sh','-c',CMD} reflectively, then: var p = RT.getMethod('exec', strArr.getClass()).invoke(rt, [strArr]); // read p.getInputStream() -> return stdout as the task result ``` The Python evaluator is analogous: `Context.newBuilder("python").allowAllAccess(true).build()`. The Conductor **community REST API has no authentication by default**, so the two calls that trigger evaluation — `POST /api/metadata/workflow` (register the malicious workflow) and `POST /api/workflow/{name}` (start it, which synchronously evaluates the `INLINE` task) — require no credentials. The task result (`GET /api/workflow/{id}?includeTasks=true` → `tasks[].outputData.result`) carries the executed command's stdout back to the attacker. **Fix commits (release 3.30.2):** - `87a7d96aabbb706d6e84f812b93da5165028d18f` — replaces `HostAccess.ALL` with a builder that `denyAccess(...)` `Class`, `ClassLoader`, `java.lang.reflect.*`, `Runtime`, `ProcessBuilder`, `Process`, `System`, `Thread`, `ThreadGroup`; removes `allowAllAccess(true)` from `PythonEvaluator`. - `c691e35e768caeb802c9f06ecdd9674c80081af1` — adds `allowHostClassLoading(false)`, `allowNativeAccess(false)`, `allowCreateThread(false)`, `allowCreateProcess(false)`, `allowIO(IOAccess.NONE)`, `allowEnvironmentAccess(EnvironmentAccess.NONE)`, and engine options `js.load=false` / `js.print=false` / `js.console=false`. Disabling host class loading is the binary discriminator that prevents the script from materializing the classes needed to reach `Runtime`. ## Reproduction Steps 1. **Reference script:** `bundle/repro/reproduction_steps.sh` (with helper `bundle/repro/exploit.py`). 2. **What the script does:** - Pulls the real Docker images `conductoross/conductor:3.22.3` (vulnerable) and `conductoross/conductor:3.30.2` (fixed negative control). - Starts the **vulnerable** Conductor with Docker `--network host`, waits for the `/health` endpoint to return 200, and confirms the unauthenticated metadata API (`GET /api/metadata/workflow` → HTTP 200, no auth). - Runs `exploit.py`, which (with no authentication) registers a workflow whose `INLINE` task carries the reflective `Runtime.exec` JavaScript payload, starts it, and polls the task result. The payload also writes a unique marker file inside the container. - Asserts the vulnerable task is `COMPLETED`, the result contains live command output, and the marker file exists inside the container. - Stops the vulnerable container, starts the **fixed** 3.30.2 image the same way, and runs the identical payload — asserting it is now **blocked** (`task_status=FAILED_WITH_TERMINAL_ERROR`, no command output, no marker file). - Writes `bundle/repro/runtime_manifest.json`. 3. **Expected evidence of reproduction:** `logs/vuln_exploit.log` / `artifacts/vuln_exploit.json` show `task_status=COMPLETED` and `rce_confirmed=true` with the command stdout (`uid=0(root)…`, the marker, hostname, OS release); `artifacts/vuln_marker_check.txt` shows the marker file present inside the vulnerable container. For the fixed build, `artifacts/fixed_exploit.json` shows `task_status=FAILED_WITH_TERMINAL_ERROR`, `rce_confirmed=false`, and `artifacts/fixed_marker_check.txt` shows the marker file absent. The script exits 0 only when both conditions hold. ## Evidence - `bundle/logs/reproduction_steps.log` — full annotated run log. - `bundle/logs/vuln_container.log` — vulnerable Conductor boot log. - `bundle/artifacts/vuln_health.json` — `{"healthy":true}` from the vulnerable instance. - `bundle/artifacts/vuln_metadata_http.txt` — `HTTP 200` (unauthenticated metadata API reachable). - `bundle/artifacts/vuln_exploit.json` — exploit result: `register_status=200`, `start_status=200`, `workflow_status=COMPLETED`, `task_status=COMPLETED`, `rce_confirmed=true`, and `task_output_result` containing: ``` uid=0(root) gid=0(root) groups=0(root) PRUVA_RCE_CONFIRMED_ PRETTY_NAME="Debian GNU/Linux 13 (trixie)" ``` - `bundle/artifacts/vuln_marker_check.txt` — `docker exec` proof that the marker file was written inside the vulnerable container. - `bundle/logs/fixed_container.log`, `bundle/artifacts/fixed_exploit.json` — fixed build: `task_status=FAILED_WITH_TERMINAL_ERROR`, `workflow_status=FAILED`, `rce_confirmed=false`, `task_output_result=null`. - `bundle/artifacts/fixed_marker_check.txt` — marker file absent in the fixed container (`No such file or directory`). - Environment: Docker `--network host` on host `muramasa` (Arch Linux kernel); sandbox reached the host-network conductor via the docker bridge gateway `172.20.0.1:8080`; vulnerable image `conductoross/conductor:3.22.3`, fixed image `conductoross/conductor:3.30.2`. ## Recommendations / Next Steps - **Upgrade to Conductor ≥ 3.30.2** immediately. The `js`/`python` evaluators no longer run with host access / host class loading enabled. - **Defense in depth:** put authentication/authorization in front of the Conductor API (the community build is unauthenticated by default); run the server as a non-root, least-privilege user; restrict which principals can register/run workflows. - **Detection:** flag workflow definitions whose `INLINE`/`LAMBDA`/`DO_WHILE`/ `SWITCH` tasks contain `expression` strings referencing `getClass`, `forName`, `Runtime`, `exec`, `ProcessBuilder`, or `java.` reflection; alert on Conductor processes spawning shells. - **Testing:** add regression tests (as the fix did in `InlineTest.java`) that assert a reflection-based RCE expression yields `FAILED_WITH_TERMINAL_ERROR` for both `javascript` and `graaljs` evaluator types on every build. ## Additional Notes - **Idempotency:** the script cleans up any leftover `conductor-vuln-repro`/`conductor-fixed-repro` containers at startup and stops/removes them after each phase, so it can be re-run cleanly. Each exploit uses a timestamped workflow name and marker, so repeated runs do not collide. - **Networking:** in the DinD sandbox, Docker bridge port publishing (`-p`) does not expose a reachable port to the sandbox container; the script therefore uses `--network host` and reaches the conductor via the sandbox's default gateway (`detect_target_ip`), falling back to `127.0.0.1` when the sandbox is the docker host itself. - **Scope of the proof:** the in-range version demonstrated (3.22.3) uses the plain `HostAccess.ALL` configuration; the 3.30.0/3.30.1 partial blocklist is not separately bypassed here — the negative control uses the complete fix (3.30.2) per the CVE's fixed version. ## Reproduction Details Reproduced: 2026-07-06T08:20:35.100Z Duration: 1493 seconds Tool calls: 258 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00236 pruva-verify CVE-2026-58138 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00236&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00236/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 - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-58138 - Source: conductor-oss/conductor ## Artifacts - bundle/repro/reproduction_steps.sh (reproduction_script, 12074 bytes) - bundle/repro/rca_report.md (analysis, 11459 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 9823 bytes) - bundle/vuln_variant/rca_report.md (analysis, 12186 bytes) - bundle/artifact_promotion_manifest.json (other, 18395 bytes) - bundle/vuln_variant/source_identity.json (other, 1352 bytes) - bundle/vuln_variant/root_cause_equivalence.json (other, 2753 bytes) - bundle/repro/exploit.py (script, 7202 bytes) - bundle/logs/reproduction_steps.log (log, 2249 bytes) - bundle/repro/runtime_manifest.json (other, 1166 bytes) - bundle/repro/validation_verdict.json (other, 1115 bytes) - bundle/logs/vuln_container.log (log, 24890 bytes) - bundle/logs/vuln_exploit.log (log, 480 bytes) - bundle/logs/fixed_container.log (log, 28344 bytes) - bundle/logs/fixed_exploit.log (log, 340 bytes) - bundle/logs/vuln_variant_steps.log (log, 16395 bytes) - bundle/logs/vuln_variant/fixed_version.txt (other, 225 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 7996 bytes) - bundle/vuln_variant/variant_manifest.json (other, 5016 bytes) - bundle/vuln_variant/validation_verdict.json (other, 3218 bytes) - bundle/vuln_variant/runtime_manifest.json (other, 2533 bytes) - bundle/logs/vuln_variant_A.log (log, 345 bytes) - bundle/logs/vuln_variant_B.log (log, 336 bytes) - bundle/logs/vuln_variant_C.log (log, 347 bytes) - bundle/logs/variant_vuln_container.log (log, 24887 bytes) - bundle/logs/variant_fixed_container.log (log, 28343 bytes) - bundle/logs/vuln_variant/vuln_version.txt (other, 60 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00236 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00236/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00236 ## 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