How AI Agents Sharing a Sandbox Leak Each Other's Data

A finance AI agent leaked bank balances into company Slack by sharing compute with another agent. Reproduced in Docker, then fixed with separate sandboxes.

On 2026-10-10, Cybernews reported that Shane Mac, CEO of XMTP Labs, gave a Grok Bot agent read-only access to his personal bank account as a finance assistant. A separate Grok Bot agent, whose job was posting updates to his company Slack, posted his balances there instead: a “monthly financial audit” listing gym memberships, a home renovation, and card payments, posing as him (HN thread , 62 points). Mac’s own explanation was specific: the two agents shared one cloud compute instance, so the Slack agent could reach data it was never given.

That’s a one-sentence root cause, and it’s worth taking at face value: “different task” is an instruction, not a boundary. I reproduced the mechanism generically, no Grok Bot involved, in two Docker Compose files that differ in exactly one line. Repo: themsquared/shared-sandbox-agent-leak .

Disclosure: I work at Solo.io, which created kagent, one of the projects mentioned below. This post is my own work, and the repro uses plain Docker with no Solo software.

The setup

Two toy agents, neither one LLM-driven, because the point is the compute boundary, not agent reasoning:

  • finance-agent writes a balance snapshot to /secrets/finance.json. That’s its entire job.
  • slack-agent posts messages to a webhook. It was never given a finance scope, and its code never references finance data.
  • mock-slack is a 20-line http.server standing in for a real Slack incoming webhook, so you can see exactly what would land in #general.

slack_agent.py does one thing: check whether a file exists at a path, and if it does, post it.

def main():
    time.sleep(2)
    try:
        with open(SECRET_PATH) as f:
            leaked = json.load(f)
        print(f"[slack-agent] found a file at {SECRET_PATH} I was never given, posting it, because that's my job")
        post_to_slack({"channel": "#general", "text": "monthly financial audit", "attachment": leaked})
        print("[slack-agent] LEAKED: posted finance data to Slack")
    except FileNotFoundError:
        print(f"[slack-agent] no file at {SECRET_PATH}, nothing to post, nothing leaked")

There’s no finance-specific logic to remove, no prompt to rewrite, no permission check that was skipped. Whether this agent leaks anything depends entirely on what’s mounted at /secrets when it starts, which is decided outside the agent, by whoever wired up its compute.

Reproducing the leak

docker-compose.vulnerable.yml mounts the same named volume into both agents at /secrets:

services:
  finance-agent:
    volumes:
      - shared-secrets:/secrets
    command: ["python", "/app/finance_agent.py"]

  slack-agent:
    volumes:
      - shared-secrets:/secrets
    command: ["python", "/app/slack_agent.py"]

volumes:
  shared-secrets:

Both containers mount shared-secrets. Run it:

docker compose -f docker-compose.vulnerable.yml up --abort-on-container-exit --exit-code-from slack-agent
mock-slack-1  | [mock-slack] #general received: {"channel": "#general", "text": "monthly financial audit", "attachment": {"checking_balance_usd": 4812.33, "line_items": ["gym membership -45.00", "home renovation -1200.00", "card payment -389.12"] }}
slack-agent-1 | [slack-agent] found a file at /secrets/finance.json I was never given, posting it, because that's my job
slack-agent-1 | [slack-agent] LEAKED: posted finance data to Slack

slack-agent did exactly what it was built to do: look for something to post, find it, post it. The leak isn’t a bug in either agent’s code. It’s the absence of a boundary between them.

The fix is one line, at the compute layer

docker-compose.isolated.yml changes a single thing: slack-agent gets its own, separate, empty volume at the same path, instead of finance-agent’s volume.

services:
  finance-agent:
    volumes:
      - finance-secrets:/secrets

  slack-agent:
    volumes:
      - slack-agent-own-sandbox:/secrets   # was: finance-secrets

volumes:
  finance-secrets:
  slack-agent-own-sandbox:

Same two agents. Same two Python files, byte for byte. Same two tasks. Run it:

docker compose -f docker-compose.isolated.yml up --abort-on-container-exit --exit-code-from slack-agent
slack-agent-1 | [slack-agent] no file at /secrets/finance.json, nothing to post, nothing leaked

slack-agent looks in exactly the same place and finds nothing, because there’s nothing there to find. Neither agent’s code changed. Neither agent’s prompt or task description changed. Neither agent is aware the other exists, before or after the fix. The entire difference is which volume got attached to which container, decided once, outside of either agent, before anything ran.

./run.sh in the repo runs both scenarios back to back if you want the side-by-side output yourself; the whole thing takes under a minute on a laptop with Docker already running.

Why this isn’t just a Docker-volume toy problem

The shared-sandbox failure mode is common enough that the Kubernetes agent ecosystem is actively standardizing the fix, because it doesn’t live in the agent’s prompt or code; it lives in whatever schedules the agent’s compute:

  • kubernetes-sigs/agent-sandbox shipped v1.0.6 on 2026-10-08. It defines a Sandbox custom resource specifically so that “run this agent” and “give it its own isolated environment” are the same Kubernetes API call, not two things a platform operator has to remember to wire together separately.
  • kagent shipped gVisor-backed microVM sandboxes in v1.0.0-alpha4 on 2026-09-25, making per-agent compute isolation a platform default rather than something each deployment has to opt into.

Neither of those is exercised in the repo; the repo is deliberately the smallest possible reproduction, runnable by anyone with Docker and no cluster. If you’re already running agents on Kubernetes, that’s where the equivalent boundary belongs, and I wrote about a related failure, an agent escaping its sandbox’s egress controls entirely over DNS, in Your Agent Egress Proxy Never Saw the DNS Query . That post and this one are two different boundaries around the same box: what an agent can reach over the network, versus what it can see sitting still on its own filesystem.

Worth being precise about what this repo does not claim: it doesn’t model credential injection, token scoping, or an egress gateway deciding what an agent is allowed to call out to. That’s a different, complementary boundary (what an agent can reach) from the one here (what an agent can see on its own filesystem). Conflating the two is exactly how a team ends up with a tightly scoped API token and a wide-open shared disk, which is closer to what actually happened in the reported incident than a token leak would have been.

Honest limits

  • Both agents here are a dozen lines of Python each, not LLM-driven. The compute boundary holds the same way with a real agent framework in place, because Docker enforces it, not either agent’s code, but I didn’t wire up a live model to prove that specific claim in this repo.
  • mock-slack is a stand-in http.server, not real Slack. The receiving side doesn’t matter to the demo; what matters is whether the read succeeds.
  • Only one failure mode is modeled: a shared named volume. A shared environment variable, a shared process namespace, or a shared cloud VM’s local disk outside Docker entirely would leak the same way for the same reason. I didn’t build separate repros for each, though the mechanism is identical in all of them.

Try it

git clone https://github.com/themsquared/shared-sandbox-agent-leak.git
cd shared-sandbox-agent-leak
./run.sh

Two agents, two jobs, one shared filesystem: one of them posts the other’s secrets to Slack. Give them separate sandboxes instead, change nothing else, and the same agent that leaked finds nothing to leak. If you’re scheduling agents for a living, that’s the whole post: the isolation has to be an infrastructure decision, because it is never going to be a prompt.

Frequently asked questions

Why did a Grok Bot AI agent leak bank account data into Slack?

Per Cybernews' 2026-10-10 report, XMTP Labs CEO Shane Mac gave a Grok Bot agent read-only access to his personal bank account. A separate Grok Bot agent, configured only to post updates to his company Slack, posted his balances there instead. Mac's own explanation: the two agents shared one cloud compute instance, so the Slack agent could reach data it was never scoped to see.

Does giving two AI agents different tasks keep their data separate?

No. A task description is a prompt-level instruction, not a compute boundary. If two agents share a filesystem, environment, or process namespace, either one can read whatever the other wrote there, regardless of what either agent was told to do. Isolation has to be enforced below the prompt, at the compute layer.

How do you isolate AI agents running on the same host or cluster?

Give each agent its own sandbox: a separate container, volume, or microVM, so there is no shared filesystem, environment variable set, or process namespace between agents. kubernetes-sigs/agent-sandbox defines a Sandbox CRD for this on Kubernetes, and kagent ships gVisor-backed microVM sandboxes as a platform default in its v1.0.0-alpha series.

What's the difference between agent sandboxing and agent credential scoping?

Sandboxing controls what an agent can see on its own machine: its filesystem, its environment, and whether another agent's data is reachable at all. Credential scoping and egress gateways control what an agent can reach over the network once it's running. They're complementary boundaries that solve different problems; this post is about the first one.