Security & Compliance

A classifier is not a sandbox: isolating coding agents

By Technspire TeamAugust 30, 20264 views

On 26 August 2026, security researcher Johann Rehberger published a working remote code execution chain against Claude Code running in auto mode, the safety configuration that replaced human approval prompts with an automated classifier and, according to his writeup, became the default for Claude Code in mid-August. In small test samples the chain succeeded in three to four runs out of five. The most uncomfortable detail is not that the classifier approved the steps that led to compromise. It is that after Claude itself detected the malware, the classifier blocked the cleanup command. If your organisation runs coding agents anywhere near production credentials, this incident is the clearest argument yet for treating model-side safety features as one layer in a stack, never as the stack itself.

What auto mode is, and what it is not

Claude Code's permission system is tiered: file edits and shell commands normally require approval, and permission modes control who gives it. In manual mode a human reviews actions; in auto mode, per Anthropic's documentation, "a classifier reviews actions instead of you." The classifier sees the proposed action and decides whether it looks safe enough to run without a prompt. That removes friction for exactly the workflows enterprises want: long refactors, dependency upgrades, batch fixes across repositories.

What auto mode is not, and what Anthropic's own documentation does not claim it to be, is an isolation boundary. A classifier is a probabilistic judgment about intent rendered on incomplete information. An isolation boundary is an operating-system or hypervisor guarantee about what a process can touch regardless of intent. Rehberger's conclusion after building the exploit: "Auto Mode IS NOT a substitute for running your agent in an isolated environment and monitoring what it is up to."

Anatomy of the break

The chain is worth understanding in detail, because none of its individual steps look malicious, which is precisely why a per-action classifier struggles with it.

Step 1: steer the tools, not the model

The attacker's web server returns an HTTP 415 error to Claude's WebFetch tool. Claude, trying to be helpful, falls back to running curl directly. Rehberger frames the principle: "An attack does not tell the model what to do. The attack just makes the malicious path the one worth pursuing to solve an objective." No injected instructions, no jailbreak text, just an environment shaped so the agent's sensible next move serves the attacker.

Step 2: the safety instinct that backfires

The download is a ZIP archive containing plausible notebook data plus two extra files: a native binary and an innocuous-looking struct.py. Claude correctly refuses to execute the unknown binary. Instead it writes its own small Python decoder to process the data, a decision that looks like good judgment and is the pivot of the whole exploit.

Step 3: module shadowing inside a poisoned directory

Claude runs its decoder from inside the extracted archive directory. Python resolves imports from the script's directory before the standard library, and importing base64 pulls in struct underneath. The attacker's local struct.py shadows the standard library module and executes obfuscated code at import time.

# decoder.py, written by the agent, run inside ./extracted-archive/
import base64   # base64 imports struct internally;
                # ./extracted-archive/struct.py shadows the stdlib module
                # and runs attacker code at import time

Simon Willison's follow-up commentary labels this a "confused environment attack": the model never follows a malicious instruction; the environment redefines what its legitimate action does.

Step 4: payload, persistence, and the blocked cleanup

The poisoned module downloads a second-stage script that deploys a native payload and opens command-and-control communications. Claude eventually notices the compromise and tries to kill the process. Auto mode denies the command. Rehberger: "The classifier allowed the creation of the malware process, but then it blocked the command intended to stop it!" The safety mechanism became part of the failure.

Why the benchmark said zero and the exploit still worked

Anthropic evaluates the classifier against a fixed suite of attack scenarios. Rehberger's chain was not among them: "The benchmark measured a fixed set of 72 scenarios... My chain was not in that set. So 0.00% on the benchmark and a working RCE are both true at once." That is not an indictment of benchmarking; it is a reminder of what benchmarks are. They measure defence against known attack shapes. Novel chains, by definition, score zero until someone builds them. Security controls that hold regardless of attack shape (filesystem boundaries, egress denial, credential absence) are the ones you can rely on when the benchmark runs out.

The operating principle: assume any agent that reads untrusted content (web pages, downloaded archives, issue text, package metadata) will eventually execute attacker-influenced code. Design the blast radius before the incident, not after. Rehberger's phrasing: "Security invariants are not optional."

The isolation ladder: matching containment to autonomy

Not every agent needs a hypervisor. The right level of containment scales with how many actions run without human review and what the agent can reach. Use this ladder as a decision framework.

  • Level 0 – Manual approval, developer workstation. A human reviews every command. Acceptable for interactive pairing on low-sensitivity repos. The human is the boundary, and human attention degrades with prompt fatigue, so keep sessions short and credentials scoped.
  • Level 1 – OS-level sandbox on the workstation. Claude Code ships a sandboxed Bash tool: you declare which files and network domains commands may touch, and the operating system enforces the boundary for every command and child process (Seatbelt on macOS, namespace and seccomp tooling on Linux and WSL2). This survives module shadowing: the poisoned import can run, but it cannot write outside the project or reach an unlisted host.
  • Level 2 – Disposable container. Dev containers or ephemeral containers with a copy of the repo, no personal home directory, no SSH keys, no cloud CLI sessions. Compromise costs you the container, which you were going to delete anyway.
  • Level 3 – Hypervisor-isolated ephemeral compute. For unattended agents in CI or scheduled automation, run execution in VM-grade isolation with per-task teardown. This is the level where Azure has a purpose-built service, covered below.
  • Level 4 – Segmented network with default-deny egress and monitoring. For long-running autonomous fleets: dedicated subnet, firewall allowlist for the handful of endpoints the agent legitimately needs (model API, package mirror, git remote), and telemetry on every tool call. The C2 stage of Rehberger's chain dies here even if everything else fails.

The selection rule: count the consecutive actions that run without human review, and inventory what a process on that machine could reach. Ten unattended actions with production credentials in the environment demands Level 3 or above. An interactive session on a clean laptop can justify Level 1. If you cannot say what the blast radius is, you have not chosen a level; you have defaulted to trusting the classifier.

Building the sandbox on Azure

Hypervisor isolation without the plumbing: dynamic sessions

Azure Container Apps dynamic sessions exist for exactly this problem. Microsoft's documentation describes them as "fast access to secure sandboxed environments that are ideal for running code or applications that require strong isolation," with Hyper-V isolation per session, prewarmed pools that allocate in milliseconds, and automatic teardown after a cooldown. The documented scenarios include safely executing AI-generated code and running untrusted code. Two pool types cover most agent needs: code interpreter pools with a built-in Python environment, and custom container pools where you bring an image with your own toolchain. Dynamic sessions are available in Sweden Central, so execution stays in-region.

az containerapp sessionpool create \
  --name agent-exec-pool \
  --resource-group rg-agents \
  --location swedencentral \
  --container-type PythonLTS \
  --max-sessions 20 \
  --cooldown-period 300

Egress: deny by default, allow by name

Rehberger's chain needed the network twice, first to fetch the second stage and then for command and control. An egress allowlist breaks both. On Azure that means running agent compute in a VNet whose outbound path goes through Azure Firewall with FQDN rules, or at minimum NSGs plus a proxy. The allowlist for a coding agent is short: the model endpoint, your git remote, and your package registry. Route packages through Azure Artifacts or an internal mirror rather than allowing the public registry wholesale, which also gives you a control point against the poisoned-package variants of this attack class.

Credentials: make theft worthless

The payload stage of any such chain goes hunting for SSH keys, cloud CLI token caches, and environment variables. The defence is absence. Agent compute should authenticate with a managed identity scoped to the few resources the task needs, CI runners should use OIDC federation instead of stored secrets, and no long-lived personal access token should exist on any machine where an agent executes. A stolen managed-identity token scoped to one storage container and expiring within the hour is a very different incident from a stolen developer PAT with org-wide write access.

Monitoring: the layer the classifier cannot replace

Ship agent telemetry (every command, every network attempt, every file write outside the workspace) to Application Insights or your SIEM via OpenTelemetry, and alert on the shapes this incident exhibited: processes spawning from freshly extracted archives, outbound connections to hosts not on the allowlist, and denied commands immediately following allowed ones. That last pattern is exactly the allowed-compromise-then-blocked-cleanup sequence, and it is cheap to detect once you know to look.

The Swedish and EU angle

The AI Act's main application date has arrived. As of 2 August 2026, the remainder of the AI Act applies, with the exception of Article 6(1); prohibitions took effect in February 2025 and general-purpose AI model obligations in August 2025. Most internal coding agents will not be high-risk systems under the Act, but the governance expectation it creates is broader than its strict legal scope: organisations deploying AI are expected to know what their systems do and to control them. An agent deployment where you can show the isolation level, the egress allowlist, and the telemetry trail is one you can defend to a DPO, an internal auditor, or a regulator. An agent running with a classifier as its only control is not.

NIS2 raises the cost of exactly this incident. For Swedish enterprises in scope, a coding agent that executes attacker-controlled code with reachable credentials is a reportable-incident vector sitting inside the development environment. Demonstrable technical controls (isolation boundaries, restricted egress, credential scoping) are what incident-handling and risk-management obligations look like in practice for agent infrastructure. The mitigations in this article map directly onto the control evidence a NIS2 audit will ask for.

Data residency holds up. Hyper-V isolated execution via dynamic sessions is available in Sweden Central, and firewall, VNet, and monitoring components carry no regional compromise. Building real agent containment does not require moving workloads out of region, which keeps the security work from colliding with residency commitments already made to your customers. For public-sector buyers, the isolation ladder above also gives procurement a concrete requirement to write into agent-related tenders instead of a vague demand for "secure AI".

What to do this week

  • 1. Inventory agent execution surfaces. List every place a coding agent runs: laptops, CI runners, scheduled automation. Record what credentials and networks each can reach.
  • 2. Assign each surface a ladder level. Anything unattended at Level 0 or with a raw classifier as its only control gets remediated first.
  • 3. Turn on OS sandboxing where agents run interactively. Claude Code's sandboxed Bash boundary is built in; declaring the filesystem and network scope takes minutes per repo.
  • 4. Strip credentials from agent environments. Managed identities and OIDC federation in CI; expire every PAT you find on a machine that runs agents.
  • 5. Stand up default-deny egress for unattended agents. Model endpoint, git remote, package mirror; nothing else until someone justifies it.
  • 6. Wire tool-call telemetry into your SIEM. Alert on denied-after-allowed sequences and on connections to unlisted hosts.

Rehberger's closing advice is the durable takeaway: do not treat an approval, whether from a tired human or a well-trained classifier, as evidence that code is safe. Approvals gate actions; sandboxes bound consequences. Run your agents where the consequences are bounded.

Sources