Warden CLI Reference
Every subcommand, flag, and exit behavior below matches the CLI source
(cmd/warden). With no subcommand, warden prints usage and exits 1.
CLI Experience
Warden has a consistent, security-focused terminal identity. Colors and banners are semantic and degrade gracefully.
-
Banner: A large
WARDENASCII banner is shown forwarden(no args),warden init, andwarden doctorwhen stderr is a TTY. It is never shown forwarden --version,warden --help, orwarden runso that scripted and sandboxed invocations stay clean. On narrow terminals (COLUMNS<60) the banner falls back to a compact header; on CI or when output is piped it is omitted entirely. -
Header:
warden doctorandwarden initshow a compact header (WARDEN+MCP Server Sandbox Runtime+Version …) when the large banner is not used. -
Colors: Green marks success/allowed/sandbox-active, red marks blocked/denied/errors, cyan is used for structural headings/paths/metadata, and normal foreground is used for body text. Colors are semantic and never the sole signalevery state also has a text marker (
✓/✗+ALLOWED/BLOCKEDwhere applicable) for accessibility. -
Color control: Honors
NO_COLOR=1(https://no-color.org),FORCE_COLOR/CLICOLOR_FORCE, andTERM=dumb. SetNO_COLOR=1orWARDEN_NO_COLOR=1to disable all ANSI. UseWARDEN_NO_UNICODE=1to force ASCII fallbacks (OK/xinstead of✓/✗). -
Non-TTY / CI: When
CIis set or stderr is not a TTY, Warden avoids animations and uses deterministic bracketed progress ([1/4] … OK) so logs stay clean. Spinners are short, never leave stray characters, and are disabled in CI. SetWARDEN_NO_SPINNER=1to force static output. -
First-run: The first time Warden is invoked interactively it shows a one-time welcome (
Welcome to Warden+ capabilities +warden init/run/doctorhints) and then never again. The marker lives at${XDG_STATE_HOME:-~/.local/state}/warden/welcomed(0600). It is suppressed in CI, whenWARDEN_NO_FIRST_RUN=1is set, or when neither stdout nor stderr is a TTY. It never blockswarden runand never requires interactive input. -
Installation: The published npm package ships only the
bin/wardenlauncher (no install script), sonpm install -g warden-sandbox-cliprints npm's standard output only. The platform binary is fetched on the firstwardeninvocationthe launcher printsDownloading warden v<version>..., downloads the matching GitHub Release binary into~/.cache/warden/<version>/, and execs it. Later runs reuse the cache. See Install for the expected output. -
Fail-closed messaging: When a backend cannot be initialized, Warden prints
✗ Warden refused to start: sandbox backend unavailablewith the specific reason andfails closed by designit will never run your MCP server without a working sandbox. This is not just UIit reflects thesandboxerr.RefuseToRunerror that prevents any unsandboxed fallback.
warden run
Run a server under a policy. The sandbox backend is auto-detected (native first, Docker fallback, fail-closed when nothing is available).
warden run --policy <file> [--backend auto|linux|seatbelt|windows|docker]
[--approve] [--approve-timeout <dur>] -- <command...>
| Flag | Meaning |
|---|---|
--policy <file> | Required. Policy YAML. Relative filesystem grants resolve against this file's directory. |
--backend <name> | Force a backend. Default auto. Aliases: bwrap → linux; macos, darwin, sandbox-exec → seatbelt; appcontainer, lowbox → windows. Unknown or unavailable backends fail closed. |
--approve | Interactive approval: prompt on first out-of-policy access instead of hard-failing. Network approvals apply live; filesystem approvals are saved to the policy and the server restarts. Requires a terminalfails closed without one. |
--approve-timeout <dur> | Per-prompt timeout (e.g. 30s, 2m). Zero waits indefinitely; expiry denies. Requires --approve. |
-- <command...> | Command to sandbox. Optional separatoreverything after the flags is the command anyway; -- lets the command itself start with a flag-like token. |
Command resolution: the CLI tail wins, otherwise the policy's command: is
used, otherwise the run fails with a usage error. Bare executable names
(npx, uvx, node) are resolved via PATH lookup and fail closed when
unresolvable; prefer absolute paths in checked-in policies. Exit code: the
sandboxed process's exit code, 1 on runtime failure, 2 on usage/policy
errors.
Run output: warden run never prints the large bannerit prioritizes
runtime information. When stderr is a TTY (and CI is not set and
WARDEN_QUIET is empty) it prints a compact pre-launch summary to stderr
only (so MCP stdio is untouched):
WARDEN
──────────────────────────────────────
Policy policy.yaml
Backend linux
Command /usr/bin/node server.js
Filesystem
✓ /workspace/project (read)
✗ everything else
Network
✓ api.github.com
✗ everything else
Environment
✓ GITHUB_TOKEN
✗ all unspecified variables
──────────────────────────────────────
✓ Sandbox active
Only information from the loaded policy is shownno permissions are
invented. In CI / piped / WARDEN_QUIET=1 the summary is suppressed so
warden run stays silent apart from the sandboxed process's own stdio.
Security events are recorded to the audit log (warden logs) as structured
allowed/blocked JSON; the CLI formats them as ✓ ALLOWED / ✗ BLOCKED
when displayed (color reinforces but never replaces the text marker).
warden trace
Run unsandboxed but instrumented; record every file/network access to the
trace log for init to consume:
warden trace -- <command...>
Prints the trace file path on stderr on exit. Exits with the command's exit code.
warden init
Generate a conservative starter policy from a trace log. Never overwrites an existing file:
warden init [--log <file>] [--output <file>] [-- <command...>]
| Flag | Meaning |
|---|---|
--log <file> | Trace log to read. Default: the latest trace. |
--output, -o <file> | Where to write. Default policy.yaml. Refuses to overwrite. |
-- <command...> | Recorded into the generated policy's command: field. |
Only successful observed accesses become grants; blocked events never do;
runtime paths (/usr, /proc, …) are omitted. Review and tighten the
output before relying on it.
warden logs
Inspect the persistent JSONL audit log
(${XDG_STATE_HOME:-~/.local/state}/warden/audit.jsonl):
warden logs [--log <file>] [--tail <n> | -n <n>] [--follow | -f]
--tail 0 prints nothing; --follow polls for appended events (survives
truncation). Prints "no audit events recorded yet" when the log doesn't exist.
warden doctor
Check that the host can actually enforce a sandbox. It probes the real
backends (bubblewrap/Seatbelt/AppContainer/Docker), strace availability on
Linux, the egress proxy, and the policy engine, and prints a
security-oriented report:
warden doctor
Example output (TTY):
WARDEN DOCTOR
Environment
────────────────────────────────
✓ Operating system linux/amd64
✓ Sandbox backend linux
✓ Namespace support available (bwrap)
✓ Network proxy available (egress allowlist)
✓ Policy engine ready (YAML + validation)
✓ Fail-closed enabled (never runs unsandboxed)
Security posture
────────────────────────────────
✓ Filesystem isolation explicit paths only
✓ Environment filtering explicit vars only
✓ Network policy explicit hosts only
✓ Fail-closed behavior enforced
Status: READY
When the backend is unavailable it prints Status: NOT READY in red,
the underlying RefuseToRun reason, and Warden fails closed when sandboxing is unavailable. The exit code is machine-readable so scripts
and CI can gate on it:
0ready to run1NOT READY (sandbox backend or a required tool such asstraceis missing)2usage error (unexpected arguments)
warden version
Print the stamped build version and exit 0. This stays script-friendly and never prints a banner:
warden version
warden --version
Single line warden version <semver> (or dev for unstamped builds).
Release builds stamp the version at link time (the npm launcher downloads
that exact version's binary); unstamped source builds report dev.
Accepts version, --version, -version, and -v.
warden add
Connect one MCP server to one MCP client in a single command, sandboxed behind Warden:
warden add slack to claude-desktop
warden add notion to claude-desktop
warden add @scope/[email protected] to claude-code \
--allow-host api.example.com --allow-env SCOPE_KEY
warden add --list-clients
The first argument is a catalog pack id (warden packs list) or any npm MCP
package (name[@version]). add prepares the pinned npm release inside the
sandbox (registry-only egress, install scripts disabled), generates a
deny-by-default policy, and registers a wrapped launcher in the client's
config after a preview + confirmation prompt.
| Flag | Meaning |
|---|---|
to <client> / --client <id> | Target client adapter: claude-desktop, claude-code, cursor, codex, vscode, gemini, cline, cascade, generic. |
--name <name> | Entry name in the client config (default: pack id / package name). |
--scope project|user | Config scope (default project; claude-desktop defaults to user). |
--config <file> | Explicit configuration path. |
--runtime <dir> | Prepared runtime directory (default ~/.local/share/warden/runtimes/..., honors XDG_DATA_HOME). |
--data <dir> | Data directory for packs that need one. |
--policy <file> | Use your own reviewed policy; its command becomes the upstream. |
--allow-host <host> | Generic npm servers: grant network egress (repeatable). |
--allow-env <name> | Generic npm servers: forward an env var name (repeatable). |
--allow-read <path> / --allow-write <path> | Generic npm servers: grant a read/write path (repeatable). |
--backend <name> | Sandbox backend (default auto). |
--yes | Apply without the confirmation prompt. |
--dry-run | Preview only; writes nothing, starts no process. |
--list-clients | Show every detected client config and its current entries (read-only; cannot be combined with other arguments). |
Behavior: catalog packs use their reviewed pack grants; any other npm package
gets a deny-by-default starter policy (no network, no env, no paths beyond
the runtime) extended only by the --allow-* flags. Credentials are reported
by name only and never written into config files. Before writing, add
probes the real sandbox with an inert Warden process and refuses to modify
the config if the backend is unavailable. Undo anytime with
warden unwrap --client <client> --server <entry>. See warden help add.
warden gateway
Wrap gateway-registered servers so the gateway launches each one sandboxed:
warden gateway init --config <file> --policies <dir>
# Per-server deny-by-default policies (<name>.yaml). Remote (SSE/HTTP)
# servers are skippedno local process to sandbox. Never overwrites.
warden gateway run --config <file> --policies <dir> --server <name> \
[--backend ...] [--approve] [--approve-timeout <dur>] -- [extra args...]
# Run one registered stdio server sandboxed. Fails closed when the policy
# is missing or the server is remote. Bare launcher names resolve via PATH.
warden gateway wrap --config <file> --policies <dir> \
[--warden-bin <path>] [--backend <name>] [--output <file>]
# Emit a gateway config whose stdio commands are prefixed with
# 'warden run --policy ...'. Point the gateway at the wrapped file.
warden gateway list --config <file> [--policies <dir>]
# Show registered servers: stdio vs remote, and policy presence.
Supported configs: Claude-style mcpServers JSON (Claude Desktop/Code,
Cursor, VS Code servers, Docker MCP Gateway clients) and gateway YAML
registries (upstreams: à la jonfairbanks/mcp-gateway, backends: à la
MikkoParkkola/mcp-gateway).
Exit codes
| Code | Meaning |
|---|---|
0 | Success (for run: the sandboxed process exited 0) |
| other | For run/trace: the sandboxed/traced process's own exit code |
1 | Runtime failure (backend unavailable, enforcement error, approval restarts exhausted) |
2 | Usage or policy error (bad flags, unreadable/invalid policy, no command) |