Security Research
By Hemal Maniar | October 11, 2026
Hello folks, in today’s blog I am going to cover my process of testing CVE-2026-58138 in Conductor without actually going through with remote code execution (RCE).
I came across a public exploit that abuses the INLINE JavaScript task
to reach Java APIs through GraalVM. Instead of running the exploit
as-is, I wanted to understand the execution path and see if I could
build a safe PoC that produces a signature I can verify, without running
shell commands or touching files.
The exploit reference is available on
Exploit-DB. The code
identifies conductoross/conductor:3.22.3 as its tested image and lists
3.30.2 as the fixed version. For this test, I used 3.22.3 as the
affected-version candidate and 3.30.2 as a control.
Scope: This is a safe, local validation exercise. The PoC does not invoke
Runtime.exec(), execute an OS command, read or write files, or make outbound connections. The result is a behavioural observation, not standalone proof that arbitrary RCE is possible.
I started a local Conductor container using Podman. Keep in mind that the version matters here, so I made sure to record which image I was testing rather than relying on the container name alone.
podman run -d --name conductor-test \
-p 8080:8080 \
-p 5000:5000 \
docker.io/conductoross/conductor:3.22.3
I then checked that the container was running and that the API was
reachable at http://127.0.0.1:8080.
podman ps
For reproducibility, it is also worth recording the image digest:
podman image inspect docker.io/conductoross/conductor:3.22.3
Before testing anything CVE-specific, I wanted to confirm that the
normal INLINE JavaScript evaluator was working.
I created a workflow that accepts two numbers and multiplies them:
{
"name": "poc_inline_check",
"version": 1,
"schemaVersion": 2,
"tasks": [
{
"name": "inline_task",
"taskReferenceName": "inline_task_ref",
"type": "INLINE",
"inputParameters": {
"evaluatorType": "javascript",
"expression": "(function(){ return $.a * $.b; })();",
"a": "${workflow.input.a}",
"b": "${workflow.input.b}"
}
}
],
"outputParameters": {
"result": "${inline_task_ref.output.result}"
}
}
I saved this as workflow_def.json and registered it:
curl -X POST \
http://127.0.0.1:8080/api/metadata/workflow \
-H "Content-Type: application/json" \
-d @workflow_def.json
Next, I started the workflow with two input values:
curl -X POST \
"http://127.0.0.1:8080/api/workflow/poc_inline_check" \
-H "Content-Type: application/json" \
-d '{"a": 173, "b": 241}'
The workflow completed and returned:
{
"status": "COMPLETED",
"output": {
"result": 41693
}
}
Nothing special here. The expression evaluated 173 * 241 and returned
41693. This was just my baseline to confirm that the API, workflow
registration, and JavaScript evaluation were functioning.
A successful arithmetic expression does not confirm the CVE. It only
confirms that the INLINE evaluator is available.
The public exploit goes much further than a normal workflow. It uses
Java reflection to resolve Java classes, reaches java.lang.Runtime,
starts a process, and returns the process output through the workflow
result.
I did not need to run that full chain to investigate the
JavaScript-to-Java boundary. Instead, I used a small Python harness,
safe_poc.py, to register a temporary workflow, start it, poll for the
result, and look for a unique canary string.
The relevant JavaScript expression was:
(function() {
var cls = $.getClass();
return "CVE-2026-58138-SAFE-CANARY:"
+ cls.getName();
})()
The canary is simply a marker that makes the expected result easy to identify. The interesting part is the method invocation, not the text of the marker.
The script performs the following steps:
INLINE JavaScript task.The script also handles the fact that the workflow-start endpoint can
return the workflow ID as plain text rather than as a JSON object.
Initially, my script assumed it would always receive JSON and failed
with a JSONDecodeError. I updated the response parsing to accept
either JSON or plain-text responses.
The important part of the expression is:
var cls = $.getClass();
$ is an object made available to the JavaScript evaluation context.
The expression asks that object for its class representation.
The next call is:
cls.getName()
This attempts to invoke getName() on the returned object. In the
affected-version test, the operation returned the following value:
java.util.LinkedHashMap
The complete output was:
CVE-2026-58138-SAFE-CANARY:java.util.LinkedHashMap
LinkedHashMap is a standard Java collection class. Its name is not the
vulnerability, and the test is not claiming that this class is
vulnerable. It is the Java-side class name returned by this particular
expression.
What I was checking was whether the JavaScript expression could complete this interaction and return a deterministic result.
On conductoross/conductor:3.22.3, the workflow completed and the
script reported:
SAFE VULNERABILITY SIGNATURE CONFIRMED
CVE-2026-58138-SAFE-CANARY:java.util.LinkedHashMap
The Conductor instance evaluated JavaScript with
Java host-object access enabled.
NO OS COMMAND WAS EXECUTED.
This established that the expression completed in this environment and produced the expected canary. It was useful evidence of the behaviour of the JavaScript evaluator and its interaction with a Java-side object.
It did not establish that every Java API was available, nor did it prove arbitrary code execution on its own.
I then ran the same safe_poc.py expression against
conductoross/conductor:3.30.2.
The workflow was registered and started, but the task failed with:
TypeError: invokeMember (getName) on java.util.LinkedHashMap failed due to:
Unknown identifier: getName
This is an important distinction: the failure happened during the JavaScript expression, not while registering or starting the workflow.
The observed results were:
Check 3.22.3 3.30.2
——————————— —————– ———————–
Workflow registration Successful Successful
Workflow start Successful Successful
INLINE task reached evaluator Yes Yes
Full canary expression Returned canary Failed at getName()
OS command executed No No
The comparison shows that the same expression behaved differently in the two test environments. However, I would not treat this single method call as definitive proof of the complete vulnerability or its remediation. A failed method invocation can depend on evaluator configuration and implementation details, so it is better described as a differential behavioural observation.
For a report, I would record the exact image tags and digests, the expression used, the full task error, and the workflow output for both runs.
Class.forName()My first version of the safe PoC tried to call forName() directly:
var clazz = $.getClass().getClass();
var stringClass = clazz.forName("java.lang.String");
That failed with an error similar to:
Unknown identifier: forName
This showed that I could not assume every Java method would be available through direct JavaScript member access. GraalVM’s interop behaviour depends on the host-access configuration and the object being accessed.
I simplified the test to use a smaller expression and recorded what actually happened rather than trying to force the original exploit’s reflection chain to work.
That was the point of keeping the test safe: I wanted to understand the observed behaviour, not turn the validation into an RCE attempt.
The tests established a few things:
INLINE JavaScript task could be
registered and started.3.22.3, the safe canary expression returned
java.util.LinkedHashMap.3.30.2, the same expression failed at the getName()
invocation.The key distinction is between the baseline and the safe validation:
Test What it tells us
———————————– ———————————–
Arithmetic INLINE workflow Normal JavaScript evaluation works
safe_poc.py on 3.22.3 The tested Java-side interaction
completed and returned a canary
Same expression on 3.30.2 The tested method invocation behaved differently
Actual RCE payload Not executed as part of this validation ———————————————————————–
The safe PoC did not test Runtime.exec(), filesystem access, or
network access. It should therefore not be described as successful RCE.
This was a useful exercise in breaking a public exploit down into smaller pieces and validating the behaviour without running the dangerous part of the chain.
The baseline workflow confirmed that INLINE JavaScript was working.
The safe PoC then tested a JavaScript-to-Java interaction and gave me a
reproducible output to compare between versions. The result differed
between 3.22.3 and 3.30.2, but this test alone does not prove the
complete RCE impact or establish that the method-call difference is the
sole reason for the patch.
For me, the main takeaway is to avoid treating a successful workflow, a Java class name, or a single error message as conclusive proof by itself. Record the exact version, preserve the raw output, compare the affected and fixed builds, and be precise about what the test actually demonstrated.
The goal here was to validate the execution path without crossing the final boundary into command execution, and that is where I stopped.
2024 - 2026 | Hemal Maniar