DEV Community

Leon Odor
Leon Odor

Posted on

What Should an Agent Be Allowed to Change?

Part 1 ended with a sealed host. The machine was locked down. I felt a surge of relief because I had solved the wrong-feeling half of the security problem in a satisfying way. The base system could not be casually mutated. That felt secure. But it created a new problem immediately. On a sealed, deliberately bare host, the agent can do nothing useful.

The naive approach is simple in theory and impossible in practice. The agent needs to install ffmpeg. It cannot. The host has no ffmpeg. It also lacks a compiler toolchain. It does not have curl or git or unzip installed on the base system. The idea of running apt install ffmpeg on the host is not dangerous anymore. It is simply impossible. Which is correct, and also useless. I had restricted the host until it became inert. The agent is a client. It has a job to do. It needs tools. It needs to be messy.

So the agent needs somewhere it can be messy without the mess landing on my machine. That somewhere is what I call a Bench. A Bench is a disposable, mutable environment. It provides fresh writable state every time it is invoked. I mount a scratch workspace at /work inside that environment. The agent can install packages, compile things, write files, and break whatever it wants inside that space. When the task is done, the Bench is thrown away. The host never changed, because the host was never in the loop.

The Bench runs as a rootless container. Podman with crun under the hood handles the actual execution. The privileged part of the operating system creates and supervises these environments. The agent never touches the container engine directly. It requests a Bench, and the operating system provides one. This separation is critical. The agent does not control the infrastructure. It only controls the workspace.

At this point I had to ask myself whether I was just reinventing rootless Podman with extra steps. The answer is yes. The execution engine IS rootless Podman. I did not invent disposable mutable environments. A container already gives you that. So if the container already exists, what am I actually adding?

The answer is not new container technology. It is making mutation itself a governed lifecycle on top of that engine. A bare podman run leaves every authority decision ad hoc. I would have to wire up the mounts by hand. I would have to hand it a network by hand. There is no default and no record. The Bench is not about the container runtime. It is about the policy layer around it.

A disposable box sounds safe because I can throw it away. But throwing it away afterward does nothing about what it could reach WHILE it ran. A default container configuration can bind-mount half my home directory. It can talk to the entire internet. An agent that is messy AND can read my SSH keys AND can POST them anywhere is not made safe by the fact that its container gets deleted at the end.

Disposability protects the host's future. It does nothing about the present blast radius.

This is the core tension. The agent needs to be able to change things. It needs to write to disk. It needs to fetch dependencies. But if the container has broad access, the damage is done before the container is destroyed. The destruction of the container is a cleanup operation. It is not a security measure. The security measure is what happens before the cleanup.

So a Bench starts with nothing. No view of my files. No network at all. This is the default deny posture. From there I grant, explicitly and narrowly. For files, I grant a specific directory. It is read only or read write. Nothing above it is visible. The grants are mounted noexec. The agent cannot execute arbitrary binaries from my home directory. It can only use what is in its own scratch space.

The grant is never "my home directory." It is "this one project folder, read write." This granularity matters. If the agent is transcribing audio, it needs access to the audio files. It does not need access to my tax returns. If the agent is installing a Python library, it needs network access. But it does not need access to my database.

For network access, I do not grant "the internet." I grant a named set of destinations. When the agent needs to run apt to install something, I grant it the Debian package egress and nothing else. It can reach the Debian mirrors and literally nowhere else. This is not just about convenience. It is about reducing the attack surface. If the agent is compromised, or if it makes a mistake, it cannot exfiltrate data to random endpoints. It can only talk to the sources it needs.

The agent asks for what it needs. The operating system records the grant. I confirm it. The confirmation path is separate from the agent's control. The agent cannot fake the confirmation. How that works is a later post. Every grant is narrow, recorded, and revocable. The agent does not get permanent authority. It gets temporary capability.

This is the shift that made the whole thing click for me. The question stopped being "do I trust this agent." That question has no good answer, because the agent is a client and a stochastic one. You cannot trust a stochastic process. You can only constrain its outcomes.

The question became "what narrow, revocable capabilities does THIS task actually need."

Transcoding a video needs the video directory and no network. Installing a Python library needs the package egress and a scratch dir. Neither needs my whole machine. Mutation stopped being a yes or no about the host and became a lifecycle with a default of no. The agent does not have rights. It has permissions granted per task.

Now disposability means something. The Bench was powerful for exactly as long as the task ran. It operated inside walls I drew. It reached only what I granted. And then it was gone. The host never noticed. That is the thing I wanted at the end of Part 1. A place where an agent could be temporarily powerful without that power becoming authority over my machine.

But there is a new problem. The agent builds something in a Bench that actually works. It installs a complex toolchain. It compiles a binary. It writes a script that solves the problem perfectly. And then the Bench is thrown away. The work is gone.

This is the tension that Part 1 ignored and Part 2 has now exposed. The agent is useful only if its work persists. But if the work persists, the host is no longer sealed. The agent has left a mark. The question of mutation resurfaces.

The obvious move is to save the Bench. To take that working environment and make it permanent. To promote it from a transient workspace to a persistent layer. But saving the whole messy box is going to turn out to be a bad idea. The Bench contains everything. It contains the tools, the dependencies, the temporary files, the logs, the cache. It contains the noise along with the signal.

So if I want to keep what a Bench became, what exactly is worth keeping?

Top comments (0)