Skip to content

Unsafe pickle deserialization of Redis event-bus messages #929

Description

@VamsiSudhakaran1

Hi — thanks for AWorld. While reading the Redis event bus I hit a deserialization pattern worth a look.

Every Message is pickled and pushed to a Redis stream, then pickle.loads'd on the way out:

aworld/events/redis_backend.py

async def publish(self, message: Message, **kwargs):
data = {"data": pickle.dumps(message)} # line 38
msg_id = await self.client.xadd(name=name, id="*", fields=data)

async def consume(self, message: Message = None, **kwargs):
...
data = pickle.loads(message_content.get(b"data")) # line 52 (also line 64, consume_nowait)
pickle.loads executes arbitrary code during deserialization, so the security boundary here is entirely the Redis instance — and it's the default transport, with no safe-serializer option.

The question this raises: is that Redis guaranteed single-tenant, network-isolated, and unreachable by anything an attacker can influence? In a multi-agent deployment the bus is often shared, and a single compromised or prompt-injected agent that can xadd to a task stream turns this into remote code execution on every consumer — after any input guardrail has already passed.

Possible direction: use a non-executing serializer for transport (JSON/msgpack over Message.to_dict()), or if pickle is required, sign payloads (HMAC) and verify before loads. Even a documented "AWorld's Redis bus must be a trusted, isolated instance" would help operators reason about it.

Surfaced with release-gate, an open-source pre-deploy checker that traces model/agent data into dangerous sinks. Happy to be wrong if the bus is provably isolated in all deployments.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions