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 sandboxwardcell 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.
wardcell config
Section titled “wardcell config”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"}.
wardcell doctor
Section titled “wardcell doctor”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.
| Section | What it answers |
|---|---|
installation | the version, which binary answered, and which config file is in force |
policy | whether the policy root is there, resolves, and what zones it defines |
plugin | which 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 |
enforcement | the floor a run would get; on macOS, every installed extension version, whether it has been approved, and whether both capture configurations are live |
records | where 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.
doctorsays so, and the repair is a reboot. - Loopback not captured. Every port the macOS floor opens is open on
127.0.0.1too 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.
Output formats
Section titled “Output formats”The inspection commands — policy explain/render/draft/verify, doctor,
config show, zone list, and logs list/logs show — take --output:
pretty | the default: for people, with colour when writing to a terminal |
text | the same, never coloured |
json | one 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.
wardcell run
Section titled “wardcell run”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 |
--yes | on a first run in a directory with no .wardcell.sbx, accept the detected toolchains and write them instead of opening the picker |
--no-init | never 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:
wardcell logs show <id> -f --output jsonThe 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.
wardcell logs
Section titled “wardcell logs”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 prunelist 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.
wardcell policy init
Section titled “wardcell policy init”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 |
--yes | accept 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.
wardcell policy draft
Section titled “wardcell policy draft”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 |
--print | print 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) |
--yes | on a first draft in a directory with no .wardcell.sbx, accept the detected toolchains without opening the picker |
--no-init | never open the first-run picker or print the new-marker hint. Also set by WARDCELL_NO_INIT=1 |
--output pretty|text|json | pretty/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.
wardcell policy explain
Section titled “wardcell policy explain”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|macos | restrict to rules applying on that OS |
--output pretty|text|json | default pretty; json is the resolved policy |
wardcell policy render
Section titled “wardcell policy render”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.
wardcell policy verify
Section titled “wardcell policy verify”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.
wardcell policy fmt
Section titled “wardcell policy fmt”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 | |
|---|---|
--check | write nothing; exit 1 and list files that are not already formatted (the CI check) |
wardcell zone list
Section titled “wardcell zone list”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|json | a table, or JSON |
wardcell zone current
Section titled “wardcell zone current”wardcell zone currentInside a sandbox only. Prints the zone this sandbox is in, alone on one line.
wardcell zone enter
Section titled “wardcell zone enter”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.
Environment variables
Section titled “Environment variables”WARDCELL_POLICY_ROOT | default policy root, overridden by --policy-root. Unset, it is /etc/wardcell/policies/. Refused when policy_root.allow_override = false |
WARDCELL_NO_INIT | set 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_SANDBOX | set to 1 in every sandboxed process; decides which commands wardcell offers |
WARDCELL_ZONE | set in a sandboxed process started in a zone: that zone’s name |
WARDCELL_ZONE_SOCKET | set in a sandboxed process started in a zone: its supervisor’s socket |
WARDCELL_CAPTURE | macOS 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 |
Exit codes
Section titled “Exit codes”0–255 | the sandboxed command’s own exit code |
1 | zone enter was denied, policy verify found the policy invalid, or doctor found a problem |
128+n | the command was killed by signal n |
2 | wardcell 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 |
3 | an 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.