system: OPERATIONAL
← back to all hacks
AGENTS MEDIUM NEW

When an agent's safest choice runs the exploit: module shadowing in auto mode

An August 2026 write-up shows a coding agent that refuses a suspicious binary and writes its own Python decoder instead — and that safe decision is exactly what runs the attacker's code.

2026-09-05 // 6 min affects: claude-code, opus-5, coding-agents, python

What is this?

On August 26, 2026, security researcher Johann Rehberger (Embrace The Red) published a walkthrough of an attack chain that turns a coding agent’s own caution into remote code execution. Simon Willison covered it the next day, August 27, and Adversa AI folded it into their September 3 coding-agent roundup. The setup is mundane: a user asks the agent to summarize a website. The result, in Rehberger’s small-sample lab runs, was arbitrary code running on the host in 60–80% of attempts — including a live command-and-control callback.

What makes the finding worth reading is not a novel bug in any one product. It is a structural point about autonomous agents: the agent never knowingly follows a malicious instruction. It makes a series of individually reasonable decisions, and the environment is arranged so that the safe-looking one is the trap. Willison, after discussion, reframed it as a “confused environment attack” rather than a classic prompt injection — no attacker text is ever obeyed as a command.

How it works

The chain is built from small, benign-looking steps:

  1. The agent fetches a page. The server answers 415 Unsupported Media Type, so the agent decides on its own to retry with curl in a shell — moving from a summarizing fetch tool to raw command execution. The server never told it to; it just made that the obvious path.
  2. curl follows a redirect to a ZIP archive of plausible catalogue records, encoded, plus a native decoder binary and a file named struct.py.
  3. The agent extracts the archive into a scratch folder and correctly refuses to run the untrusted binary.
  4. Instead, it writes its own small Python decoder and runs it — from inside the extracted directory.

Step 4 is the exploit. Python puts the current directory on its module search path, and the standard-library base64 module imports struct. Because the archive contains a file called struct.py, Python loads the attacker’s version first — a technique called module shadowing. The malicious module re-exports the real low-level API so decoding continues normally, while an obfuscated expression executes during import. In the lab, that expression launched an isolated child process that pulled down a later stage and opened a C2 channel.

# The agent's reasoning, roughly:
#   "The supplied binary is untrusted -> don't run it."
#   "I'll write my own decoder instead -> safe."
#   python3 -c 'import base64, zlib, json; ...'   # run from ./extracted-archive/
#
# What actually happens:
#   base64 imports struct  ->  ./struct.py (attacker) loads first  ->  code runs

The core observation, in the researcher’s words: the agent does not trust the supplied binary, but it trusts the decoder it wrote itself. The decoy binary exists precisely to push the model toward writing its own code in a poisoned directory.

Why it matters

Two details make this more than a party trick. First, the vendor’s own layered defenses were reported to reduce indirect prompt injection to near-zero on a fixed benchmark of 72 scenarios — yet this chain, which was not in that set, worked most of the time. A headline “0.00%” and a working exploit can both be true; a single benchmark number does not describe resilience against a novel chain.

Second, and more striking: in some runs the agent noticed the compromise afterward and tried to kill the malware process — and the auto-approval classifier blocked the cleanup command. The same safety layer that had allowed the malicious process to spawn then prevented its termination. A best-effort classifier that gates commands by apparent intent is not a containment boundary, and it can occasionally work against you.

This pattern generalizes well beyond one agent or one language. Any agent that (a) can be nudged from a constrained tool into raw shell execution, (b) writes and runs code in an attacker-controlled working directory, and (c) relies on an intent classifier rather than OS isolation, is exposed to the same shape of attack.

Defenses

The reporting agent and researcher agree on the same conclusion: the model’s judgment is not the safety net. The boundary has to be enforced by the runtime around it.

  • Sandbox unattended agents. Run coding agents in a container, VM, or OS-level sandbox — not directly on a workstation with real credentials. This is the actual security boundary; the classifier is not.
  • Restrict network egress. Default-deny outbound traffic so a spawned stager cannot reach a remote payload or a C2 endpoint.
  • Never run code from an untrusted working directory. When an agent must execute a script it wrote to process downloaded content, run it from a clean, empty directory — not from inside the extracted archive.
  • Use Python isolated mode for such steps. Invoking python3 -I drops the current directory from the module search path and ignores environment overrides, which neutralizes this specific shadowing vector. The agent sometimes did this on its own; make it the default in your harness.
  • Keep secrets out of the agent’s reach. Do not expose home directories, SSH keys, or cloud credentials to the runtime, so a successful chain has nothing valuable to steal.
  • Monitor, and treat approval as convenience, not proof. An auto-approved command is not evidence that it is safe. Log tool calls and watch for shell fallback, downloads, and child-process spawns.

Status

ItemDetail
DisclosureEmbrace The Red (Johann Rehberger), August 26, 2026
CoverageSimon Willison, August 27, 2026; Adversa AI roundup, September 3, 2026
Reported success rate60–80% in small-sample lab runs
Vendor responseReport closed as “Informative” / working as designed; auto mode described as a best-effort convenience feature, not a security guarantee
NatureConfused-environment / module-shadowing chain, not a single patchable bug; no CVE assigned

All figures are the researcher’s own results on specific lab setups and small samples, and should not be read as a general success rate for any product.

Sources