Start here
Quickstart
Install Warden, confine a first command, add precise authority and preserve MIR plus execution evidence.Warden runs one command under a policy you write, and hands you back proof of what that command was actually allowed to do. This page takes you from an empty shell to a confined run with evidence on disk.
The idea in one picture
Policywhat the command may touch
Canonical MIRcompiled, portable decision
Backendthe OS mechanism that enforces it
Confined runsupervised start to exit
Receiptwhat was enforced, provable after the fact
- Policy → Canonical MIR
- Canonical MIR → Backend
- Backend → Confined run
- Confined run → Receipt
You describe the boundary once. Warden compiles it, picks a backend that can enforce every part of it, supervises the process, and records the result. If no backend can enforce the policy, the run is refused rather than weakened.
Each stage has a name you will meet again throughout these docs. A policy is your intent. MIR is that intent compiled into a canonical, platform-independent decision. A backend is the concrete operating-system mechanism — a sandbox profile, a namespace, a microVM — that actually blocks a forbidden read or connection. A receipt is the record of which backend enforced which decision.
Install Warden
macOS on Apple Silicon
Seatbelt needs macOS 15 or newer. Every method below installs the same binaries.
The tap now publishes bottles, so this downloads a prebuilt binary instead of compiling.
brew install maelys-dev/tap/maelys-warden
maelys-warden --versionPick whichever method you already trust. Homebrew and the distribution packages keep Warden upgradable in place; the archives pin one exact release; building from source is the path when you need to read or change the code before you run it.
Which backends you get
A base install compiles in two backends. The rest are optional driver packages you install only when you need OCI images or microVM isolation.
| Backend | Platform | Ships in | Enforces |
|---|---|---|---|
posix | any | base install | nothing — no confinement |
seatbelt | macOS, Apple Silicon | base install | files, network, process tree |
bubblewrap | Linux | base install | files, network, process tree, seccomp |
apple-container | macOS 26, Apple Silicon | optional driver | OCI image in a VM |
apple-container-bubblewrap | macOS 26, Apple Silicon | optional driver | OCI image, plus recursive rules inside |
krun | macOS, Apple Silicon | optional driver | OCI image in a libkrun microVM |
krun-bubblewrap | macOS, Apple Silicon | optional driver | microVM, plus recursive rules inside |
Two consequences are worth internalising now. Bubblewrap is Linux-only and never
ships on macOS, so on a Mac, Seatbelt is your only confining backend without an
optional driver. And posix applies no confinement at all: it exists for hosts
where nothing stronger is available, must be selected explicitly, and is never
chosen for you over a backend that actually enforces something.
Confine a first local command
maelys-warden run --ro --workspace "$PWD" -- codex
maelys-warden run --rw --workspace "$PWD" -- npm test -- --runInBandrun starts one supervised execution. --ro makes the workspace readable but
not writable; --rw permits writes there. Everything after -- is the workload
command, forwarded unchanged — in the second example the first -- belongs to
Warden and the second is passed through to npm test.
Both flags do more than the workspace itself. They also grant the minimal runtime
and the exact executable as readable, a private temporary directory as writable,
set the network to none, and make every descendant process inherit the same
confinement. Nothing else on the host is reachable, including your home directory.
Run a coding agent under Warden
Coding agents ship their own approval prompts and their own sandbox. Once Warden confines the process, that second layer is redundant and its prompts only ask about actions Warden has already decided. Turn it off and let Warden be the single boundary.
The catch is that an agent still needs its own credentials and session state, and
those live outside the workspace — so a default --ro run cannot even
authenticate. Grant that directory explicitly, keep the workspace root read-only,
and open write access only where the agent is supposed to edit:
maelys-warden run --ro \
--workspace "$PWD" \
--allow-read "$HOME/.claude" \
--allow-write "$PWD/src" \
--allow-write "$PWD/tests" \
--net api.anthropic.com:443 \
--net registry.npmjs.org:443 \
-- claude --dangerously-skip-permissions \
-p "Fix the failing unit tests, then stop."--dangerously-skip-permissions turns off Claude Code's permission prompts. Its
own Bash sandbox is already off by default, so there is nothing else to disable.
maelys-warden run --ro \
--workspace "$PWD" \
--allow-read "$HOME/.codex" \
--allow-write "$PWD/src" \
--net api.openai.com:443 \
--net registry.npmjs.org:443 \
-- codex --yolo--yolo is the documented alias for --dangerously-bypass-approvals-and-sandbox.
Codex ties approvals and sandboxing to one switch, so a single flag disables both.
That flag is only safe inside an already-hardened external sandbox — which is
exactly what Warden provides here.
Grant $HOME/.claude and $HOME/.codex read-only, as shown, unless you want the
agent to persist session history across runs; then grant write instead. What you
are not granting matters just as much: SSH keys, cloud credentials, ~/.npmrc,
~/.netrc and ~/.git-credentials all stay outside the boundary, so a prompt
injection in the repository cannot reach them. The API hosts above are examples —
check what your agent version actually contacts and grant only those.
Add only the authority the tool needs
maelys-warden run --ro \
--workspace "$PWD" \
--allow-write "$PWD/build" \
--net registry.npmjs.org:443 \
-- npm ci--allow-read and --allow-write extend the filesystem decision. Each
--net HOST:PORT adds one mediated destination, meaning the connection is
proxied and recorded by Egress rather than opened directly by the workload. Warden
then selects the strongest eligible backend and fails closed if none can enforce
the result.
For ordinary tools, --network-frontend auto selects the Egress proxy whenever a
mediated destination exists. Use --network-frontend fd4 only for a Maelys-aware
program that implements the fd 4 protocol itself.
Keep the decision and the evidence
maelys-warden run --ro \
--workspace "$PWD" \
--emit-mir /tmp/reviewed-policy.mir \
--receipt /tmp/execution.json \
-- /usr/bin/true--emit-mir writes the canonical MIR before the process starts, so the exact
decision can be reviewed, hashed or replayed later. --receipt writes the
terminal execution receipt after Warden has waited for the workload. Workload
stdout stays separate from both files.
A receipt is what a completed run leaves behind:
{
"schema": "maelys.execution-receipt/v1",
"invocationId": "build-2026-08-24-001",
"decisionDigest": "94d14d6aece6e3a0cdf1409d9a92695b8591fe7016ae56ebad221fcd49ba8bc3",
"planDigest": "cf14f5b4eff7f425b140948c9c1d53f905c73e7ad6504aaa1d3e04490f812535",
"mediator": "maelys-netd/proxy-v1",
"backend": {"name": "bubblewrap", "version": "0.9.0"},
"capabilities": {"required": 16415, "backend": 24575},
"network": {"mode": "mediated", "frontend": "netd-proxy"},
"startedUnixMs": 1787541910254,
"forcedStopAttempted": false,
"completed": true,
"outcome": {"exitCode": 0, "termSignal": 0}
}planDigest and decisionDigest are content-addressed identities: hashes of the
compiled policy and of the decision Warden enforced. Recompiling the same policy
reproduces the same digest, which is how you prove after the fact exactly what a
run was allowed to do. backend names the mechanism that enforced it, and
capabilities compares what the plan required against what that backend offered.
network.mode says whether traffic was blocked, direct or mediated through Egress.
Correlate network activity
One run produces one execution receipt, but it can open many connections, so
those are recorded separately. Add --network-receipts FILE and Egress appends one
JSON object per line as each connection finishes — ten connections, ten lines.
run_id='ci-4821-attempt-2'
maelys-warden run --ro \
--workspace "$PWD" \
--net github.com:443 \
--invocation-id "$run_id" \
--receipt execution.json \
--network-receipts connections.jsonl \
-- curl https://github.com/You now have two files describing one run, and --invocation-id is what ties
them together: Warden writes that value into the execution receipt, Egress writes
it into every connection line. Without it a tool reading both files would have to
guess at the pairing from filenames or timestamps; with it, execution.json and
every line of connections.jsonl carry the same ci-4821-attempt-2. It is a
correlation label only — not a secret, and not a grant of any kind.
A single line, pretty-printed here for readability:
{
"invocationId": "ci-4821-attempt-2",
"principal": "maelys",
"host": "github.com",
"port": 443,
"outcome": "closed",
"bytesFromClient": 812,
"bytesToClient": 64213
}Refused connections are recorded too. If the workload tries to reach a destination the policy never allowed, Egress closes the stream and still emits a line for the attempt, with an outcome marking the refusal — an empty file means no connection was attempted, never that something slipped through unseen. Lines carry destination, outcome, byte counts and policy identity; never payloads.
The file stays empty when the network is none, and also under --net direct,
since ambient direct networking bypasses Egress entirely and therefore cannot be
recorded. Exact field names are illustrative pending a published schema for this
file; the execution receipt above is the one with a normative schema today.
Going further with policy
The flags used above — --ro, --rw, --allow-read, --allow-write, --net —
are Warden's shorthand, and they resolve most single-command runs:
maelys-warden run --ro --workspace "$PWD" --allow-write "$PWD/dist" -- make buildThat shorthand builds a MIR for you. Authoring policy directly buys you what it
cannot express: explicit DENY rules that override a broader grant, view
mappings that show a process a different path than the real one, per-rule literal
or recursive scope, and one reviewed policy file shared across many commands and
machines instead of a shell line rewritten each time.
Warden accepts such a policy as --policy file.json or as already-compiled
--plan file.mir. The language, its compiler and the full authoring reference
are a product of their own: see policy.maelys.dev.
Verify a downloaded release
sha256sum --check maelys-warden-0.41.0-linux-x86_64.tar.gz.sha256
gh attestation verify maelys-warden-0.41.0-linux-x86_64.tar.gz \
--repo maelys-dev/maelys-wardenTagged releases publish macOS arm64 and Linux amd64/arm64 archives, .deb and
.rpm packages, SHA-256 checksums and provenance attestations. See
release integrity for signatures, package names and
the complete verification model.
Choose the next guide
This quickstart gets you to a first successful confined run. The tutorials go further: they walk the JSON policy to MIR to receipt pipeline end to end, add mediated networking, and cover the platform-specific backends.
- macOS tutorial: Seatbelt, fd 4, Egress and optional OCI drivers.
- Linux tutorial: Bubblewrap, proxy relay and native tests.
- Policy and canonical MIR: what MIR is and where it comes from.
- CLI reference: every command, option and invalid combination.
- Backend capability matrix: what each backend can actually guarantee.
- C integration or Node.js and Python: embed Warden in another product.