sharness.ai / enforcing the process tree · this page as markdown

Enforcing the process tree

Status: design. The proof of concept has not been run. Every side door below marked "not yet measured" is untested. As of 24 Oct 2026 the proof of concept was started and not completed; running it and publishing the results is an open task.

Routing Bash through a component is easy (Agents and LLMs). Making it impossible to skip is the real requirement: a prompt-injected agent will simply try /bin/sh, python -c, a binary written to the workspace, or a signal to the sandbox server.

1. Principle

Enforce at the root of the process tree, before the harness starts, with restrictions that are inherited and cannot be removed.

2. Design

 sharness.launcher  (outside every domain; platform, or the script that starts the agent)
   │
   ├── sharness.bash-server   own uid, OUTSIDE the orchestrator's domain
   │      for each request: new pids cgroup → unshare (net, pid, mount) → drop to sandbox uid
   │      → Landlock profile → exec bash -c <cmd> → filter output → return
   │
   └── orchestrator (Claude Code / own loop)
         uid: not root; capabilities: none; no_new_privs
         Landlock domain (applied by the launcher before exec):
           EXECUTE only: node (itself), rg, sharness-bash-client, MCP server binaries
           READ:         /usr, /lib, /etc, the app's own files
           WRITE:        its own state folder only (NOT the workspace: that is the sandbox's)
           TCP connect:  sharness.llm port, sharness.bash-server port
           scope:        signal + abstract-unix (ABI 6+): cannot signal or reach anything outside
         │
         ├── execve("/bin/bash")            ✗ EACCES  (no execute right)
         ├── execve("/usr/bin/python3")     ✗ EACCES
         ├── execve("./work/payload")       ✗ EACCES  (workspace has no execute right)
         ├── kill(<bash-server pid>, …)     ✗ EPERM   (Landlock signal scoping)
         └── bash-client ──socket──► bash-server ──► sandbox (fresh uid, pidns, netns, Landlock, pids≤64)

The ✗ outcomes in the diagram are the designed results; none has been measured yet.

Why a server, not a wrapper

Landlock layers only narrow. If the orchestrator's domain allowed executing /bin/bash so that a wrapper could exec it, the orchestrator could exec /bin/bash directly. The sandbox therefore has to be created by something outside the domain. The client is a messenger; it cannot run anything itself.

3. Side doors and how each is closed

Side doorClosed byStatus
exec a shell or interpreter directlyLandlock EXECUTE allowlistnot yet measured
write a binary into the workspace and run itthe workspace has no EXECUTE rightnot yet measured
an interpreter already allowed (node) does file or network work in-processthe orchestrator's own Landlock file and port policydesign
execute from memory (memfd_create + fexecve)Landlock if the memfd is outside every rule; otherwise the vm.memfd_noexec=2 sysctl and seccomp denying execveatnot yet measured
signal or ptrace the bash serverLandlock signal scoping; ptrace across domains deniednot yet measured
hooks (they are shell commands)hooks go through the bash client, or their binaries are on the allowlistdesign
binaries the orchestrator legitimately needs (ripgrep, git, MCP servers)learned by tracing the orchestrator and pinned in the manifestdesign
change the orchestrator's config (settings, /etc/hosts)no write right to those paths in the domain; manifest hashdesign
root undoes the locksthe orchestrator is not root; locks applied by the launcher, above itdesign

4. Safety rules for testing inside a live agent session

Testing on the session's own harness is like disabling the network adapter over RDP: one mistake and the session cannot reach the model any more.

  1. Never touch the orchestrator or the session bridge.
  2. Never add firewall rules that match uid 0 or the model API address.
  3. Never change global sysctls the harness depends on (e.g. vm.memfd_noexec: the harness may exec from memfd).
  4. Run every experiment as a child subtree that ends when the test ends: a mini orchestrator under its own uid and Landlock domain.
  5. Clean up cgroups, sockets and users the test created; leave a list of anything left behind.

5. The proof of concept (not yet run)

A mini orchestrator (Python, standing in for Claude Code) and a bash server (standing in for sharness.bash-server), both started by a small launcher in a child subtree.

Results: none yet. The rows above marked "not yet measured" remain to be tested by the building agent, following the safety rules in §4. When they are, the results go on Evidence with method and date.

Source: 07-enforcing-the-process-tree.