Leash v4
This is the stored snapshot for the approved document version. The diff below shows what changed from the previous version.
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
leashCLI 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.shlays down/opt/leash/v1, a default/etc/leash/leash.yaml(preserved on reinstall), a systemd unit, theleashwrapper, and a virtualenv;uninstall.shremoves 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.
| Surface | Mechanism | Enforcement |
|---|---|---|
| Network (TCP) | nftables --uid-owner redirect → transparent proxy | Kernel-enforced for the leashed UID |
| Filesystem | fanotify FAN_OPEN_PERM | Kernel-enforced for the leashed UID (privileged daemon) |
| Commands | fanotify FAN_OPEN_EXEC_PERM | Kernel-enforced for the leashed UID (privileged daemon) |
| Tools | registry + input inspection library | Cooperative — enforced by the harness (Sentinel) |
| Network (IPv6 TCP) | nftables ip6 --uid-owner redirect → same proxy | Kernel-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 intercepted | Outside 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
Hostheader 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
- Getting started and v1 scope:
v1/README.md - How it works (technical):
HOWITWORKS.md - Plain-English overview:
PLAINENGLISH.md - Help desk:
HELPDESK.md - CLI reference:
CLI.md
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)