DeepConcepts

Agentic Security / agent / isolation / enforcement point

Prompt-Level Versus Process-Level Isolation

The misconception

That 'the agent is sandboxed' is one fact. It is four different facts about four enforcement points, and the useful question is which set of actions each point sits astride. A regex over proposed shell commands is bypassed by the shell's own grammar; a container blocks the process and not the Markdown image the chat client fetches; a domain allowlist that includes any host the attacker can read — a paste site, a package registry, your own telemetry endpoint, DNS — is an open channel measured in bits per request, not a wall. Teams pick a layer, feel done, and never enumerate the primitives that go around it.

14 min

"The agent is sandboxed" is four different claims wearing one word. A rule in the system prompt, an allowlist in the harness, a kernel sandbox around the process, and a policy on the network namespace are four enforcement points, and each one mediates only the actions that physically pass through it. The useful question is never "is it sandboxed". It is "which set of actions does this thing sit astride, and which does it not".

A layer mediates an action when the action cannot happen without passing through it. A container mediates syscalls made by the processes inside it. It does not mediate a request made by your chat client on your laptop, or a job that runs tomorrow on a continuous-integration runner because the agent committed a file today. Those are not sandbox escapes. Nothing escaped: the action was never inside.

Below is a list of fourteen concrete things a compromised agent could be induced to do — this is a defender's inventory, not a recipe, and every one of them is a documented class rather than a working exploit. Configure the four layers and read which primitives each one is even in the path of. Start from the default, which is what most teams mean when they say the agent is sandboxed: a command allowlist, a container, and an outbound HTTP proxy with a host allowlist.

what your environment happens to include

The five environment toggles are not defences. They are facts about the deployment that decide whether a primitive exists at all, and they are the part nobody writes down. DNS stands for the Domain Name System, the lookup an agent performs before almost any outbound request; CI stands for continuous integration.

primitives that still reach their effect
chance at least one lands, given the prompt rules
system prompt: actions it names
harness gate: blocks / sits astride
process sandbox: blocks / sits astride
network policy: blocks / sits astride
survivors no layer could ever sit astride
effect classes still reachable
The mediation matrix: which layer is in the path of what

this layer is in the path and stops the action · in the path and lets it through · the system prompt names this action, which is a request rather than an enforcement · a dash means the layer is not in the path of this action at all, which is a different and much worse thing than letting it through · no layer means the opposite problem — this layer would be in the path, but you have not configured one. Select any row to read the reason for each cell.

What is real and what is a model. The mediation relation is real and is the whole point: each primitive declares which layers are physically in its path, and a layer is asked to rule only on the actions that reach it. The primitive list is a defender's inventory of documented classes — it is deliberately not exhaustive, and a real review of your own system will find primitives that are not here. What is modelled is the single compliance number for the system prompt: real refusal rates depend on the model, the wording and whether the agent is being steered, and nothing here measures yours.

At the default — the configuration almost everyone describes as sandboxed — 11 of 14 primitives still reach their effect. Read the three layer readouts and the reason becomes arithmetic rather than opinion. The harness gate sits astride 13 of the fourteen and blocks 1. The container sits astride 12 and blocks 1. The network policy sits astride 4 and blocks 1. Each layer is doing exactly what it says on the tin, and the tin covers a narrow strip of the surface.

The survivors no layer could ever sit astride readout says 1. That one is the Markdown image: the model emits ![](https://collector.example/…) in its reply, the reply is delivered to a chat client on somebody's laptop, and the client makes the request. The harness never sees it because no tool was called. The container never sees it because it is a different process on a different machine. The network policy never sees it because it is a different network namespace. There is no layer to turn up. This is the fifth enforcement point, it is the renderer, and it usually belongs to a different team.

The harness gate is not the sandbox, and neither is the prompt

Select the row shell out through an allowlisted binary's own config and read the four reasons. The harness gate is in the path and lets it through, because the binary is on your allowlist and the grammar that turns it into a shell belongs to the binary rather than to you. The sandbox is in the path and lets it through, because starting a shell is an ordinary syscall. The network policy is not in the path at all. Move the harness gate to allowlist plus argument inspection and that row flips to stopped; the hero number goes from 11 to 10 of 14.

Now select run arbitrary code through an allowlisted interpreter with argument inspection still on. It survives, and the reason given is the honest one: the argument is a program, so inspecting it for intent is the halting problem wearing a regular expression. There is no argument policy that distinguishes a test script from an exfiltration script, because the difference is in what the program does when it runs. Only no shell at all, typed tools only stops it, and that takes the hero number to 9 of 14 at the cost of an agent that can no longer run a command you did not anticipate.

This is the split people miss when they say sandboxed. The harness gate is policy over proposed tool calls and it is bypassed by grammar: the grammar of a shell, of a binary's own configuration flags, of an interpreter. The kernel sandbox is policy over syscalls and it is bypassed by scope: anything not executed by that process tree. They fail in completely different ways and neither substitutes for the other. Put the harness gate back to command allowlist — so that only the sandbox is moving — and set the sandbox to container plus a seccomp syscall filter. Watch which row changes: only the kernel-syscall one, taking the hero number from 11 to 10 of 14. A seccomp filter is attack-surface reduction against the kernel, not policy about what your agent may do, and reading it as the latter is how a container ends up described as a permission system. The process-level machinery underneath is the ordinary namespaces-and-cgroups stack, with all of its usual leaks.

The system prompt is not a layer at all, and the readouts say so. It names 6 of 14 actions — those are the ones somebody thought to write a rule about — and the compliance slider does nothing to the other eight. Put the sandbox back to container: namespaces and cgroups, so you are standing on the defaults again and the hero number reads 11 of 14. Now push model follows the written rule to 100% and the chance at least one lands readout stays at 100.0%, because a perfectly obedient model still has no rule telling it not to encode a secret in a hostname. Prompt rules are an enumeration exercise, and the enumeration is always shorter than the primitive list. This is the same reason instruction-hierarchy defences are priced in residual rate rather than in guarantees. Put the slider back to 95% — it is load-bearing for a number two sections down, and it is the easiest control on this page to leave somewhere by accident.

Turn everything up and read what is left

Set the harness gate to typed tools only, the sandbox to read-only rootfs, seccomp, no host mounts, the network policy to deny all outbound, and switch on the write gate. This is the most locked-down agent anyone has proposed to you, and it can no longer install a dependency, run a test through an interpreter, write outside its own tree, or open a socket. The hero number is 4 of 14.

Those four are worth naming, because they are what the entire layer stack does not cover. The agent can read a secret and put it in the reply, where whoever reads the transcript is the exfiltration channel. It can emit a Markdown image that the chat client fetches. It can commit a workflow file that CI runs tomorrow with CI's token, which is a privilege escalation into a process your sandbox has no relationship with. And it can edit its own instructions file, so the next session starts compromised — which is memory poisoning with the retrieval step replaced by "always in the context window".

Three of those four are named by a prompt rule; one is not. That is why the chance at least one lands readout still says 100.0%. Now switch off the chat client renders Markdown images in replies — a change made entirely in the renderer, at an enforcement point that does not appear in any of the four selects — and the readout drops to 14.3%. Switch off the CI runner too and it is 9.8%. Those two toggles moved the number further than every layer control on the page, and neither of them is a sandbox.

Then go the other way for the calibration. Put everything back to where this lesson started — command allowlist, container, HTTP proxy allowlist, no write gate, image rendering and CI back on — and switch on the container runtime socket is mounted inside. The hero number goes from 11 of 14 to 12 of 15, with exec as host joining the reachable effect classes. One line in a compose file, added so the agent could build images, and the container is now a suggestion. The read-only rootfs, seccomp, no host mounts level is the only one that takes it away, and it takes the bind mount with it.

What the specifications actually say, and what they leave to you

The Model Context Protocol — MCP, the standard interface for handing an agent a set of tools — is unusually explicit about the layer distinction in its Security Best Practices document. Under Local MCP Server Compromise it tells clients to "execute MCP server commands in a sandboxed environment with minimal default privileges", to "launch MCP servers with restricted access to the file system, network, and other system resources", and to "use platform-appropriate sandboxing technologies (containers, chroot, application sandboxes, etc.)". Every one of those is a process-level control, and the document is careful that none of them are prompt text. It also states the thing worth putting on a wall: MCP servers run with the same privileges as the client.

What no specification does is tell you which actions each of those technologies sits astride, which is the question this page exists to make answerable. That gap is why "we run it in Docker" and "we run it with a system prompt that forbids destructive commands" get treated as points on the same scale. They are not on the same scale. One of them is an enforcement point and the other is a request, and the matrix above will tell you the difference for any action you can name.

It is also why the loudest signal in this area is people trying to remove the controls. The single most-reacted issue in a sweep of agent tooling was a feature request to add a flag that skips permission prompts, at 93 reactions, and a bug report that a bypass flag stopped working carries 65. Read that as a measurement rather than as recklessness: the harness gate is the layer that generates friction proportional to how much work the agent does, and when a control's cost is felt on every action and its benefit is one blocked primitive in thirteen, people turn it off. Where that pressure ends up is the approval prompt, and what volume does to it.

Checking this on a system you actually run

Write the matrix for your own deployment. Two columns are enough to start: the action, and for each layer you believe you have, whether that layer is physically in the path. The test for "in the path" is mechanical — name the process that performs the action and the machine it runs on. If it is not the agent's process tree, the container is not in the path. If no tool call is issued, the harness is not in the path. If the connection originates somewhere other than the agent's network namespace, the egress policy is not in the path. Most teams discover their inventory has three or four rows where every layer column is a dash, and those rows are the entire risk.

Then verify the layers you think you have, rather than the ones in the README. Inside the running container, check the process-id namespace with readlink /proc/self/ns/pid and compare it to the host's; list mounts with findmnt and look for anything from the host, including the runtime socket; read /proc/self/status and check the Seccomp and CapEff fields, because a container with Seccomp: 0 has no syscall filter no matter what the orchestration says. For the network, do not test whether a blocked host is blocked — test whether an allowed host accepts an arbitrary path and body, and whether a DNS query for a name you control reaches your authoritative server. That last test takes two minutes and answers the question the allowlist cannot.

Instrument the layer that has no configuration. Render model output through a sanitiser that rewrites or drops remote image sources and link targets in agent transcripts, and log what it dropped. Point the container at a resolver you control and log every query name with its length. Route agent HTTP through a proxy that logs the full URL rather than the hostname, because registry.example.com in a log tells you nothing and registry.example.com/v1/<base32> tells you everything. These are the three highest-yield changes available, and none of them is a sandbox setting.

Finally, get the environment toggles into review. The five on this page — image rendering, a CI runner that executes what the agent commits, a host bind mount, DNS, a mounted runtime socket — are each one line in a config file, each added for an unrelated reason, and each one adds or removes a primitive without changing anything anybody would call a security setting. Put them in the same review as the tool manifest, because they decide the same thing: what set of actions exists, which is the input to every question about what the agent is permitted to do.

Why this concept is on the site

Topics are chosen from places engineers visibly get stuck, and the sources are kept with the lesson so the claim is checkable.