Skip to content

Agent shims

On this machine, the claude and codex commands are not the real programs. They are wrappers that build the walls first, then start the agent inside them.

Most of the enforcement plane only works if it actually runs. The gate is a hook — an agent started without the hook never calls it. Landlock is a kernel wall — but someone has to install it before the agent’s process starts. The shims solve that: ~/.local/bin/claude and ~/.local/bin/codex sit earlier on the PATH than the real binaries, so typing the normal command gets you the wrapped version. There is no separate “safe mode” to remember to use. The safe mode is the only mode.

operator types: claude
~/.local/bin/claude (the shim — scripts/operator-authority-claude-code-shim.py)
├─ 1. load AGENT_POLICY.yaml ── fails? ─▶ refuse to start (exit 1)
│ + log "shim-init-failed"
├─ 2. install Landlock on itself
│ (kernel wall — inherited by every child process)
├─ 3. open a session row on the dashboard
│ (operator can see the agent is live)
└─ 4. exec claude-real ── the agent starts, already inside the wall

Step 4 uses exec, which means the shim becomes the agent process rather than starting it as a child it could lose track of. The Landlock rules installed in step 2 are a property of the process itself, inherited by every shell command and subprocess the agent ever spawns. There is no later moment where they can be removed — the kernel does not allow loosening, only tightening.

Codex runs less interactively than Claude Code, so its shim (scripts/operator-authority-codex-shim.py) wraps the whole run in a before-and-after check on top of the same Landlock floor:

  • Before: take a snapshot of the worktree — a content hash of every file — so there is an exact record of the starting state.
  • Run: start Codex inside Landlock, same as above.
  • After: run the post-hoc sweep against the snapshot. Every file that changed is checked against the policy and the worktree’s scope manifest.

If the sweep finds a change Codex was not allowed to make, the shim prints the violations and exits with code 2 — the same “this is a block, not a warning” convention the gate uses. The calling automation sees a hard failure, and the violation is in the log before anyone has to notice it by hand.

Commands: Codex’s native approval, hardened

Section titled “Commands: Codex’s native approval, hardened”

The shim guards file writes (Landlock + the sweep). Shell commands are governed by Codex’s own approval system, set to untrusted by default for this repo: ~/.codex/config.toml marks /home/mareekkk/steelmoth as trust_level = "untrusted" (overriding the trusted parent), so interactive Codex escalates any non-trivial command to the operator for approval instead of auto-running it.

Two honest limits. Codex’s rule grammar can’t express the policy’s globby denies at all, so interactive command safety rests on the untrusted-escalation. And the unattended Morpheus drain runs Codex with danger-full-access — with no operator to answer an escalation, command approval is effectively bypassed there. Closing that edge by routing the drain’s shell commands through the gate’s denied_commands (shim-installed PATH wrappers) is in progress; it’s tracked on Known gaps.

Both shims report the start of a run to the dashboard’s control plane, which records who the agent is, which worktree it’s in, and what scope it claims to have. When the run ends, the session is closed. The operator’s “Active Sessions” panel is therefore a live list of every governed agent on the machine — and an agent that doesn’t appear there is, by definition, running outside the shims and worth investigating.