Security Research

CVE-2026-58138 - Safe Validation of Conductor INLINE JavaScript

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.

Setting up Conductor

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

Establishing a baseline with an INLINE task

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.

Building a safe validation PoC

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:

  1. Connects to the local Conductor API.
  2. Registers a workflow containing an INLINE JavaScript task.
  3. Starts the workflow and captures the workflow ID.
  4. Polls the workflow result.
  5. Checks whether the task output contains the expected canary.

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.

Understanding the canary

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.

Result on Conductor 3.22.3

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.

Comparing the result with Conductor 3.30.2

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.

Why I did not use 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.

What did the safe PoC achieve?

The tests established a few things:

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.

Conclusion

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