← Back to document history
Document version

Leash v4

This is the stored snapshot for the approved document version. The diff below shows what changed from the previous version.

Preview
Source path
leash/README.md
Source commit
No commit recorded
Created at
Oct 15, 2026, 6:59 AM UTC
Source digest
d7b0c90fd05358a8dacdb20b39b11fef94127658b69a55261bfe9e785a85feb9

Document snapshot

Leash

CLI reference — Every command, flag and choice, generated from the CLI's own parser.

Summary

Leash is the per-user egress and action enforcement layer for local AI agents. It attaches to a single host user — the account the agent runs as — and hard-enforces what that user is allowed to do across four surfaces: the network it can reach, the files it can open, the commands it can run, and the tools the agent may call. Every decision is keyed to that user's UID and is a plain allow or block. Leash is Linux only: it leans on Linux kernel primitives (nftables UID matching and fanotify permission events) to enforce against the agent in a way it cannot bypass.

Status

Preview. This is a v1 preview build, validated on Linux. There is no public download; it is installed locally from source on a root-capable Linux host. It is not cross-platform.

What v1 is

v1 is a single privileged service plus a leash CLI and a YAML config. You name a leashed user (e.g. fido), run the agent as that user, and Leash governs four control surfaces:

  • Network — outbound connections by domain, IP, or port.
  • Filesystem — file and directory access by path.
  • Commands — process execution by binary.
  • Tools — agent tool calls by name, with input inspection for secrets, PII, and privilege-escalation patterns.

Each surface has a mode:

  • follow-whitelist (default-deny) — only the entries you list are allowed.
  • follow-blacklist (default-allow) — everything is allowed except the entries you list.

v1 is enforce-only. A violation is a hard block: a reset network connection, an EPERM on a file open or exec, or an error returned to a tool call. There is no approval loop in v1 — nothing pauses to ask a human. That interactive fail-to-approval flow is deferred to Sentinel (v2).

A v1 install gives you:

  • The enforcement service — one privileged process that starts the enabled enforcers: a transparent network proxy and a fanotify permission daemon for the leashed UID.
  • The leash CLI on PATH — for attaching the user, editing each surface's lists and mode, validating config, checking status, dry-running a decision, tailing the decision log, and attaching named policies to individual agents (leash profile, leash bind).
  • A JSONL decision log — every allow/block is written as one JSON line, with sensitive tool-input matches masked.
  • Install/uninstall tooling — install.sh lays down /opt/leash/v1, a default /etc/leash/leash.yaml (preserved on reinstall), a systemd unit, the leash wrapper, and a virtualenv; uninstall.sh removes that footprint.

Per-agent policy

A host can carry many agent accounts, and the config file above is the floor: one policy, every subject. On top of it you can attach named policies to individual agents:

sudo leash profile save web-research --from web-research.yaml
sudo leash bind agent:leash-iris web-research
sudo leash bindings

Two tiers, global < agent: where an agent's profile sets a value it wins, where it is silent the floor stands, and list entries from both are combined. Teams are not a policy tier — to give a whole team one policy, bind the same profile to each of its agents (each binding carries its own acknowledgements). A team: scope existed until 2026-10-14 and is now refused.

The floor is always on, and widening it needs explicit consent. A profile may tighten the floor freely. Anything that widens what a subject may do is refused unless that binding acknowledges it by field and value (--ack 'network.mode:blacklist'); the refusal lists every loosening it found and prints the whole command to re-run. The acknowledgement is recorded on the binding, never in the profile, because one profile is reused by many subjects.

Scopes are Linux account names (agent:leash-iris) — Leash discovers its subjects from the host's passwd database, so that is the only identity it has, and a scope outside that namespace is refused rather than written as a binding that matches nobody. See the CLI reference for the full surface.

Fidelity (stated honestly)

Network, filesystem, and commands are kernel-enforced for the leashed UID by privileged daemons — the agent cannot dodge them by ignoring proxy variables or spawning subprocesses. Tools enforcement is cooperative: it is a library the agent harness (Sentinel) calls before running a tool. v1 ships it as a CLI/library so it is testable now and drop-in for the harness later.

SurfaceMechanismEnforcement
Network (TCP)nftables --uid-owner redirect → transparent proxyKernel-enforced for the leashed UID
Filesystemfanotify FAN_OPEN_PERMKernel-enforced for the leashed UID (privileged daemon)
Commandsfanotify FAN_OPEN_EXEC_PERMKernel-enforced for the leashed UID (privileged daemon)
Toolsregistry + input inspection libraryCooperative — enforced by the harness (Sentinel)
Network (IPv6 TCP)nftables ip6 --uid-owner redirect → same proxyKernel-enforced for the leashed UID
Network (UDP)nftables inet filter chain → drop (DNS to host resolvers excepted)Kernel-enforced for the leashed UID
Network (raw sockets)not interceptedOutside the boundary — needs CAP_NET_RAW

How it relates to Muzzle and Sentinel

The three products are complementary, and Leash shares Muzzle's engine/CLI/decision-log shape so Sentinel can drop both in:

  • Muzzle inspects the content of model I/O — it is a transparent proxy that reads each request, response, and tool call and applies prompt-injection / secret / PII / content policy. Leash does not read network payloads (it reads only the destination from the HTTP Host header or TLS SNI, with no decryption); it governs where the agent can go and what it can do. Leash's tool-input inspection reuses Muzzle's detector patterns.
  • Sentinel is the agent harness. It is where Leash's tools surface becomes a first-class gate, and where the fail-to-approval loop lands: a blocked action emits an approval request, a human approves, and Leash allows it (one-time or as a persistent whitelist add). v1 deliberately ships the enforce-only foundation so that approval layer can be added on top without reworking the core.

More

Diff from previous

--- README@3
+++ README@4
@@ -48,13 +48,44 @@
   enforcers: a transparent network proxy and a fanotify permission daemon for the
   leashed UID.
 - **The `leash` CLI** on PATH — for attaching the user, editing each surface's lists
-  and mode, validating config, checking status, dry-running a decision, and tailing
-  the decision log.
+  and mode, validating config, checking status, dry-running a decision, tailing the
+  decision log, and attaching **named policies to individual agents**
+  (`leash profile`, `leash bind`).
 - **A JSONL decision log** — every allow/block is written as one JSON line, with
   sensitive tool-input matches masked.
 - **Install/uninstall tooling** — `install.sh` lays down `/opt/leash/v1`, a default
   `/etc/leash/leash.yaml` (preserved on reinstall), a systemd unit, the `leash`
   wrapper, and a virtualenv; `uninstall.sh` removes that footprint.
+
+## Per-agent policy
+
+A host can carry many agent accounts, and the config file above is the **floor**: one
+policy, every subject. On top of it you can attach *named policies* to individual
+agents:
+
+```bash
+sudo leash profile save web-research --from web-research.yaml
+sudo leash bind agent:leash-iris web-research
+sudo leash bindings
+```
+
+Two tiers, **global < agent**: where an agent's profile sets a value it wins, where it
+is silent the floor stands, and list entries from both are combined. **Teams are not a
+policy tier** — to give a whole team one policy, bind the same profile to each of its
+agents (each binding carries its own acknowledgements). A `team:` scope existed until
+2026-10-14 and is now refused.
+
+**The floor is always on, and widening it needs explicit consent.** A profile may
+tighten the floor freely. Anything that *widens* what a subject may do is refused
+unless that binding acknowledges it by field **and value** (`--ack
+'network.mode:blacklist'`); the refusal lists every loosening it found and prints the
+whole command to re-run. The acknowledgement is recorded on the **binding**, never in
+the profile, because one profile is reused by many subjects.
+
+Scopes are Linux **account** names (`agent:leash-iris`) — Leash discovers its
+subjects from the host's passwd database, so that is the only identity it has, and a
+scope outside that namespace is refused rather than written as a binding that matches nobody. See the
+[CLI reference](CLI.md) for the full surface.
 
 ## Fidelity (stated honestly)