Skip to content

CLI Reference

wardcell <COMMAND>
config show this machine's settings from /etc/wardcell/config.toml
doctor report whether this machine would actually enforce a run
run resolve the policy and exec a command under it
logs list runs and read a run's log
policy policy-file operations that execute nothing
zone list zones, and enter one from inside a sandbox

wardcell shows only the commands that work where it runs. Inside a sandbox that is zone list, zone current and zone enter, and only in a sandbox in a zone; config, doctor, run, logs and policy are hidden and refused. On the host, zone current and zone enter are hidden and refused. See Listing and Entering Zones.

Terminal window
wardcell config show [--output pretty|text|json]
wardcell config get <KEY>

Prints the settings in force from /etc/wardcell/config.toml, and which file they came from. get prints one, by dotted name — policy_root.install_bundled, policy_root.allow_override or workspace_policy.mode. Read-only: the file is root’s to change. show prints TOML; --output json prints {"source", "config"}.

Terminal window
wardcell doctor [--policy-root <DIR>] [--output pretty|text|json]

Answers the question nothing else on an installed machine can: would a run started now actually be enforced, and if not, why not. Run it before the first real run on a new Mac, and again whenever something that should have been refused was not.

It reports and never repairs. Activating a system extension or enabling a capture configuration prompts for admin credentials and changes what every process on the machine is subject to, so where something is wrong doctor prints the command that fixes it and stops.

SectionWhat it answers
installationthe version, which binary answered, and which config file is in force
policywhether the policy root is there, resolves, and what zones it defines
pluginwhich program this machine names, and what its handshake says it implements — started to find out, since nothing about the file on disk says what it answers. A bare name is resolved on $PATH, exactly as a run resolves it
enforcementthe floor a run would get; on macOS, every installed extension version, whether it has been approved, and whether both capture configurations are live
recordswhere a run’s log goes, and the log show commands that find what a profile refused

Two enforcement findings are worth knowing about in advance:

  • A version awaiting uninstall. It is gone at the next reboot but still holds the extension’s Mach control name, so a newly activated provider cannot acquire it — and a provider that answers nothing is indistinguishable from one that was never installed. doctor says so, and the repair is a reboot.
  • Loopback not captured. Every port the macOS floor opens is open on 127.0.0.1 too and Seatbelt cannot say otherwise, so a policy needing any port at all is refused rather than quietly rendered narrower.

It is also the one command that still reports when /etc/wardcell/config.toml does not parse. Every other command refuses outright — a run built from settings nobody could confirm is worse than no run — but a machine in that state is precisely the one with a question, so doctor names the parse error, says that what follows is read from the built-in defaults, and gives the rest of the report.

0 when nothing is wrong and 1 when something is, so it can be the first line of a setup script without anything parsing it. --output json prints {"ok", "findings"}, each finding carrying section, label, value, and problem/fix when there is one.

The inspection commands — policy explain/render/draft/verify, doctor, config show, zone list, and logs list/logs show — take --output:

prettythe default: for people, with colour when writing to a terminal
textthe same, never coloured
jsonone JSON document on stdout — the stable interface for scripts. logs show streams the matching JSONL instead, one object per line

pretty and text may change between releases; json is what to parse. wardcell run prints only startup lines and a summary and takes no --output; read the run’s log for the machine-readable stream.

Terminal window
wardcell run [OPTIONS] -- <command> [args...]
Flag
--policy-root <PATH>policy root. Defaults to $WARDCELL_POLICY_ROOT, then /etc/wardcell/policies/
--profile <PATH>fragment to run under, root-relative without .sbx (e.g. toolchains/rust). Rejected when the machine defines zones.toml
--zone <NAME>zone to run under. Defaults to the default = true zone; an error when the root defines no zones
--yeson a first run in a directory with no .wardcell.sbx, accept the detected toolchains and write them instead of opening the picker
--no-initnever open the first-run picker or print the new-marker hint. Also set by WARDCELL_NO_INIT=1

The first run in a directory with no workspace policy opens the setup picker when stdin and stderr are both a terminal; otherwise it prints one hint line and continues with no workspace policy.

A run prints a few startup lines and a one-line summary; everything else — every request, grant and denial — goes to the run’s log, one JSON object per line. Read or tail it with wardcell logs, from a second shell while the run is still going:

Terminal window
wardcell logs show <id> -f --output json

The exit code is the command’s, so on its own it cannot say whether a 2 came from the command or from wardcell. The log’s run.exited event carries exit_source: command when the exit code is the command’s own, and wardcell otherwise — a launcher’s own failure (sandbox-exec exits 71 for a program it cannot execute, bwrap 1) is reported as wardcell’s, not mistaken for the command’s.

Terminal window
wardcell logs list [--all] [--status running|exited|crashed] [--output text|pretty|json]
wardcell logs show <run-id> [-f|--follow] [--category <c>] [--level <l>] [--source <s>] [--since <dur>] [-n <count>] [--output pretty|json]
wardcell logs prune

list is a table of runs, newest first (the running runs plus the last 24 hours, or everything with --all); show views or tails one run’s log, and a bare show picks the single running run; prune runs the retention sweep now. <run-id> is a full id or a unique prefix, like a git short hash.

show filters with --category (run net fs exec proxy zone audit diag), --source (cli proxy backend supervisor plugin mitm), --level, --since and -n; --output json is the matching JSONL, for jq. See Viewing Logs for the console summary, the event schema, retention and examples.

The subcommands below run in the order you reach for them: init to set a workspace up, draft to learn what a command needs, then explain, render and verify to inspect what a policy resolves to.

Terminal window
wardcell policy init [--policy-root <PATH>] [--yes]

Scans the working directory for the marker files the bundled fragments declare in offer_on and opens an interactive picker of the toolchains and integrations the checkout shows, writing the chosen set as the directory’s .wardcell.sbx requires. On an existing file it opens prefilled from the current requires and rewrites only that list, leaving every other field, comment and grant untouched.

Flag
--policy-root <PATH>policy root, for the catalog of offerable fragments
--yesaccept the detected set without opening the picker — for a script or a non-terminal, where the picker cannot run

The picker needs a terminal; without one, --yes is the way to set a workspace up. See Setting one up.

Terminal window
wardcell policy draft [OPTIONS] -- <command> [args...]

Runs the command in learn mode and drafts a .sbx fragment from what it was denied. By default the fragment is written into the working directory’s workspace .wardcell.sbx, with each observed path under the home, working or scratch directory tokenised to ~/$WORKDIR/$TMPDIR so the result is portable. With --print it goes to stdout instead and nothing is written — the command’s own output goes to stderr either way. The exit code is the command’s. See Drafting.

Flag
--policy-root <PATH>policy root
--profile <PATH>fragment to run under and draft for, root-relative without .sbx. Rejected when the machine defines zones.toml
--zone <NAME>draft under this zone
--printprint the fragment on stdout and write nothing (otherwise it is written into the workspace’s .wardcell.sbx, creating it, or adding only what it does not already say)
--yeson a first draft in a directory with no .wardcell.sbx, accept the detected toolchains without opening the picker
--no-initnever open the first-run picker or print the new-marker hint. Also set by WARDCELL_NO_INIT=1
--output pretty|text|jsonpretty/text: a writing draft reports on stderr, --print puts the fragment on stdout, denials table on stderr. json: exit status, counted denials and the fragment in one document

Like run, a first draft in a directory with no workspace policy opens the setup picker on a terminal. By default the choice is saved to .wardcell.sbx; with --print the choice applies only to this draft and nothing is written. A writing draft needs a working directory a workspace policy can govern — not the home directory or the root — and is refused elsewhere unless you pass --print.

Terminal window
wardcell policy explain [OPTIONS] [-- <command> [args...]]

Prints every effective rule with its originating fragment and layer. The command is used only for selects_on matching — it is never executed.

Flag
--policy-root <PATH>policy root
--profile <PATH>fragment to resolve, root-relative without .sbx
--zone <NAME>resolve this zone’s profile for display
--app-bundle <ID>test selects_on { app_bundles } (macOS)
--os linux|macosrestrict to rules applying on that OS
--output pretty|text|jsondefault pretty; json is the resolved policy
Terminal window
wardcell policy render [OPTIONS] [-- <command> [args...]]

Prints the backend-compiled profile — SBPL on macOS. Same selection flags as explain, plus --os to render for a platform other than the host. --output json prints {"backend", "profile"}, the profile as a string.

Terminal window
wardcell policy verify [--policy-root <PATH>] [--output pretty|text|json]

Validates schema_version and the merge invariants for a policy root. --output json prints {"ok": true, ...} or {"ok": false, "error": ...} on stdout. Exits 1 when the policy does not verify.

Terminal window
wardcell policy fmt [OPTIONS] [PATHS...]

Rewrites .sbx files into one canonical form — fixed section and rule order, consistent whitespace, comments preserved — so a hand edit does not show up in a diff as a reflow. A directory argument is searched for .sbx files; with no path, the policy root ($WARDCELL_POLICY_ROOT, then /etc/wardcell/policies/) is formatted. See Formatting a Policy.

Flag
--checkwrite nothing; exit 1 and list files that are not already formatted (the CI check)
Terminal window
wardcell zone list [--output pretty|text|json]

On the host: every zone in the machine’s zones.toml, with its profile, enter_from and description. Inside a sandbox: the current zone and the zones this run may enter, as the run’s supervisor reports them.

Flag
--output pretty|text|jsona table, or JSON
Terminal window
wardcell zone current

Inside a sandbox only. Prints the zone this sandbox is in, alone on one line.

Terminal window
wardcell zone enter <ZONE> -- <command> [args...]

Inside a sandbox only. Asks the supervisor to run the command in another zone. The command can itself list and enter zones, but never reach a zone that any zone before it could not have entered directly. Zones nest at most 8 deep.

WARDCELL_POLICY_ROOTdefault policy root, overridden by --policy-root. Unset, it is /etc/wardcell/policies/. Refused when policy_root.allow_override = false
WARDCELL_NO_INITset to anything but empty or 0 to turn off the first-run setup picker and the new-marker hint, the same as --no-init on run/draft. For CI and scripted runs
WARDCELL_SANDBOXset to 1 in every sandboxed process; decides which commands wardcell offers
WARDCELL_ZONEset in a sandboxed process started in a zone: that zone’s name
WARDCELL_ZONE_SOCKETset in a sandboxed process started in a zone: its supervisor’s socket
WARDCELL_CAPTUREmacOS only. Unset captures transparently wherever the system extension answers, and takes the proxy-variable fallback where none does. fallback forces that mode on a Mac that could capture; transparent refuses to run rather than fall back — see macOS
0–255the sandboxed command’s own exit code
1zone enter was denied, policy verify found the policy invalid, or doctor found a problem
128+nthe command was killed by signal n
2wardcell itself failed — bad flags, a policy that does not resolve, missing root, a command refused where it runs, a program that could not be executed
3an internal error in wardcell

2 always means the command never ran; so does 1 from a denied zone enter. A policy that does not resolve is 2 for every command that needed it to run or print something, and 1 only for policy verify, whose answer it is.