for agents/llms.txtv0.13.68

Home / Articles / Process behaviour policies: the most locked-down Linux process we could build inside Claude's own cloud container, derived from one traced run, enforced by the kernel, for about a tenth of a second

Process behaviour policies: the most locked-down Linux process we could build inside Claude's own cloud container, derived from one traced run, enforced by the kernel, for about a tenth of a second

By · 2026-10-24 · article v1.0.0 · newsroom v0.13.68 · behaviour-policieslinuxleast-privilegesandboxingsgitlandlockseccomparticle

Abstract: The behaviour policy family has policies for agents, decisions, workflows, skills and applications. This one is for a process. It started with sgit and keys: we want sgit to work with PKI keys rather than holding a vault key, so the vault key has to live somewhere else, in a small process acting as a key service or a network broker. That only helps if sgit's own process cannot reach the key, which led to a bigger question. Inside a cloud container, everything runs as root, which is bad practice and no defence in depth. So what is the most locked-down process Linux lets us build there? We built it, in this newsroom's own Claude Code container, and measured it. A policy is derived from one traced run: every file the process reads, every system call it makes, every host it talks to. Then the kernel enforces it with seven walls: a user that exists for one run, an empty read-only root holding only the listed files, its own namespaces, Landlock, a seccomp allowlist, a network broker that only opens tunnels to the policy's hosts, and limits. A probe that, as root, could read /etc/shadow, the agent's home and the internet could do none of it under the policy. The cost was 92 to 187 milliseconds a run. And the size of the policy is a measure of the software: Hello World in C needs 1 file and 12 system calls; sgit needs 1,025 files and 58.

Seven walls around one process, set from its policy and enforced by Linux inside this cloud container: an ephemeral user with no capabilities; an empty, read-only root holding only the listed files; its own namespaces; Landlock for paths, exec and TCP ports; a seccomp allowlist of the system calls the traced run made; a network broker that only opens tunnels to the policy's hosts; and cgroup limits.

In short

Why a process

We already have agent behaviour policies, decision and workflow policies, skill behaviour policies and, since php2shell, application behaviour policies. I want to add one more, for the thing everything else eventually runs as: a process. On Windows and on Linux, it is all processes.

This came from a practical question. We want to run sgit with PKI keys rather than vault keys. The logic is that we do not want to give the vault key to the sgit process, but it has to live somewhere. So the idea is a small separate process, an agent of its own, acting like a key management service, or a network broker that adds what is needed to a request on the way out. The key is not the risk made the general point: credential injection is a good pattern, and the first question is still what the process can do. Because if we do this, we need to make sure the sgit process itself cannot reach the key, the broker's memory or its files.

And that is where it gets uncomfortable. Inside a cloud container like the one this newsroom's agent runs in, processes run as root. Every process can read every file, signal every other process and talk to anything on the network. That is bad practice, and it is not defence in depth. So why not give sgit a process of its own, and while we are at it, lock it down properly: define everything it may do, and have the operating system refuse everything else?

The question, made concrete

What is the most locked-down process we can create today, with whatever this environment offers? If a process prints "Hello World", or runs a Python program that needs a few files, it should not be able to do anything else. It should have, in effect, an immutable file system. It should run as a user that exists only for that run, with access to a couple of directories, a couple of hosts, and nothing else Linux provides: no other executables, no other processes, no way to look around.

And the policy should list every file the process needs, every dependency, all the way down. This is the blast radius idea, the footprint and blast radius of an agent, applied to a process: reverse-engineer what it actually needs, and make that the limit.

What the container offers

This newsroom's agent runs in a Claude Code cloud container: Ubuntu 24.04 in a Firecracker microVM, Linux 6.18, as root with nearly every capability. That microVM is already a wall around the whole container. What we wanted was walls inside it, around one process. We checked what was available, and it is a lot:

Nothing extra needed installing.

Derive the policy from a run

The policy is not written by hand. A script runs the command once under strace -f and reads what it did: every file it opened successfully, every directory it listed, every program it executed, every system call it made, every connection it opened. It adds what the trace cannot see, the ELF interpreter the kernel loads for each binary and the interpreter named on a script's first line, and writes it all down as a policy.

The smallest one, for Hello World in C, reads in full: one file, the program itself; no directories; no devices; no network; and twelve system calls, from arch_prctl to write. Nothing else. The policy also records the user (ephemeral, no groups, no capabilities, no new privileges), what the process may see of other processes (nothing), and its limits (64 processes, 512 MB, two minutes).

Enforce it with seven walls

A second script takes the policy and builds the process's world, then a small static launcher applies the last walls from inside it and starts the program:

  1. An ephemeral user. useradd makes one for this run, and it is removed afterwards. The launcher drops to it, with no groups and no capabilities, and sets no_new_privs, so nothing it runs can gain privileges.
  2. An empty, read-only root. A fresh, tiny filesystem in which each file the policy lists is bind-mounted read-only at its own path. Then the whole root is made read-only, and the process is put inside it. For Hello World, the root holds one file. /etc/shadow, /root and /bin/sh are not refused: they do not exist.
  3. Its own namespaces. Mount, process, IPC, hostname and cgroup. Inside, the process sees one process: itself. If the policy has no network, it also gets a network namespace with nothing in it.
  4. Landlock. Read access to the listed files, execute access only to the listed programs, write access only to the listed folders, TCP connect only to the broker's port, and no signals to anything outside.
  5. A seccomp allowlist. Only the system calls the traced run made. Everything else returns "not permitted".
  6. A network broker. When the policy names hosts, the process's only way out is a small proxy, run outside the sandbox, that opens a tunnel to those hosts and refuses everything else, plus an iptables rule that rejects any other connection from that one user.
  7. Limits. A cgroup caps the number of processes and the memory; a timer caps the run.

The broker is the shape of the key service. In this test it only decides where a process may go. The next step is for it to hold the credential, so the process never does.

Did it hold?

To find out, we wrote a probe: a small program that tries everything a sandboxed process should never do, and reports each result. We ran it three ways.

What the probe tried, and what stopped it: as root in the container today, it could read /etc/shadow and the agent's home, write to /usr/lib and /tmp, see 92 processes, start a shell, mount a filesystem, make a user namespace, reach the internet and the agent's proxy, and open a netlink socket; under the Hello World policy, all of it was refused; with seccomp and Landlock switched off, the other walls still refused all but the netlink socket.

That is the point of defence in depth. No single wall has to be right, and switching one off does not open the door.

We also tested the parts that do not show in that table:

sgit, under its own policy

Then the real target. We traced sgit initialising a vault, committing a file and checking its status, in one working folder, and ran the same thing under the policy. It worked: the vault was created, the file committed. And the trace caught something worth knowing. sgit status talks to the server even when there is nothing to push, so the policy needed its host, dev.send.sgraph.ai, through the broker. A policy derived from a run tells you what the software actually does, not what you assumed.

What it costs

We expected the setup to be the expensive part, and at first it was: building the root with a shell script, calling mount three times per file, took 1.7 seconds for 169 files. Doing the same with direct system calls from one small program brought it to about 60 milliseconds, whatever the number of files.

The size of a policy, and what it costs: Hello World in C, 1 file, 12 system calls, no hosts, 5 ms bare, 103 ms of policy overhead; Hello World in Python, 37 files, 34 system calls, 30 ms bare, 92 ms overhead; Python fetching one page, 169 files, 44 system calls, one host, 565 ms bare, 149 ms overhead; sgit init, commit and status, 1,025 files, 58 system calls, one host, 3,495 ms bare, 187 ms overhead.

The overhead, the median of five runs, is between 92 and 187 milliseconds: making the user, the firewall rule and the limits, building the root, and cleaning up. The run itself is within noise of the bare run. For sgit, whose clone already takes from seconds to tens of seconds, that is 5% on a three-and-a-half-second job. And most of it can be done in advance: the user, the rules and the root can be prepared before they are needed. Making the user alone is about 20 milliseconds, and a uid that simply is not in use would do without it.

A policy of policies

This is also a great example of a policy of policies. There is a policy for the operating system, one for the environment, one for Python, one for sgit, and one for this execution. Each narrows the one before it and never widens it, the way decisions at every level may narrow the level above. The process gets only what is left.

A policy of policies: the operating system (what Linux can do here), the environment (this container's proxy, CA bundle and paths), the runtime (Python and its standard library), the application (sgit, its dependencies and its server), and this execution (one command, one folder, one host, one user for one run), each narrowing the one above.

And, as with hooks, the policy executes nothing. It says what is allowed; the runtime enforces it. Hooks are kernel code argued that an agent's rules should move from hope into enforcement. This is the same move, one level down, into the kernel itself.

The size of the policy measures the software

There is a bonus. Once a process has a policy, you can see how well engineered it is by the size of what it needs. The smallest possible Linux process has a tiny policy: Hello World in C needs one file and twelve system calls. Hello World in Python needs 37 files and 34. A Python script that fetches one page needs 169 files. sgit needs 1,025 files, 66 MB and 58 system calls: 544 files of its own modules, 231 from Python's standard library, 122 from osbot_utils and 85 from cryptography, 15 MB on its own.

That is not a criticism; it is a map. It says where the attack surface is, which dependencies carry the weight, and what a smaller, purpose-built sgit for one job could drop. It is all about least privilege, and you cannot have least privilege until you know what the least is.

What this does not claim

Where this comes from

My voice memo of 24 October 2026. The experiment, run that day in this newsroom's Claude Code cloud container by this site's agent: the code, the four policies and the results are in the data pack. The kernel interfaces are documented in the Linux kernel's Landlock and seccomp pages. Related: application behaviour policies, a skill behaviour policy, allowed is not right, a policy beside the prompt, approve at altitude, hooks are kernel code, the key is not the risk, footprint and blast radius. Written up and checked by this site's agent, newsroom.writer; the editorial responsibility is mine.

Threads

Agents & policyStartups & strategy This article as a graph →

No other article links here yet.

All articles · All graphs

Article No. 276 · start a reading list with it

Want the next issue by email. One issue a week or so: what was published, what it adds up to, and what is worth your time. Subscribe to the SGit Newsroom →

← All articles

Reviews

Reviews on the record, by agents, the editor and the desk, against the criteria. Each is kept for good and answered finding by finding. All reviews →

No reviews yet.