CLI reference
Every sealr and sealr-verify subcommand, flag, environment variable and exit code.
Two binaries ship in every release archive:
sealr— the recorder. Initializes, enrolls, intercepts, records, enforces, exports.sealr-verify— the offline verifier. Reads a.sealbundle and reports on it. No network, no trust in the Console.
Synopsis
sealr [--home <PATH>] [--json] <COMMAND> [OPTIONS]Global flags
| Flag | Default | What it does |
|---|---|---|
--home <PATH> | $SEALR_HOME, else the platform data dir | Recorder home directory. Overrides everything else. |
--json | off | Emit JSON instead of text, where the command has a JSON form. |
-h, --help | — | Help for sealr or for a subcommand. |
-V, --version | — | Print the version string and exit. |
--json is honoured by init, enroll, status, doctor, policy show, policy test, export
and verify. run, proxy and hook ignore it — their stdout belongs to the wrapped process or to
the agent's protocol.
Where the recorder home lives
Resolution order: --home, then $SEALR_HOME, then the platform default.
| Platform | Default home |
|---|---|
| Windows | %LOCALAPPDATA%\sealr |
| Linux | $XDG_DATA_HOME/sealr, or $HOME/.local/share/sealr when XDG_DATA_HOME is unset |
| macOS | $HOME/.local/share/sealr — there is no Application Support special case |
If none of those variables resolve, sealr exits 30 with cannot determine a home directory.
The layout inside it is documented in configuration.
sealr init
Writes a local config. This is the offline path: no account, no Console.
sealr init
sealr init --endpoint https://console.example.com:50051| Flag | Default | What it does |
|---|---|---|
--endpoint <URL> | "" (empty) | Console endpoint. Empty means offline mode: sync_enabled stays false and nothing is sent anywhere. |
--stream-id <ID> | generated ULID | The stream id to record under. Supply one only if you are resuming a known stream. |
In offline mode init also generates a self-signed local trust chain (root, recorder certificate) so
bundles you export verify offline without the Console. With an --endpoint set it generates only an
identity key, and enroll replaces it with a Console-issued certificate.
Exit code: 0, or 30 if the home directory cannot be created or written.
sealr enroll
Binds this device to a tenant using a single-use enrollment token from the Console.
sealr enroll --endpoint console.example.com:50051 --token ENROLL_xxx --label env:prod --label team:payments| Flag | Default | What it does |
|---|---|---|
--token <TOKEN> | required | The single-use enrollment token. |
--endpoint <ENDPOINT> | required | Console gRPC endpoint. An endpoint without http:// or https:// is prefixed with http://. |
--label <LABEL> | none | Environment label, repeatable. Stored in the config and attached to records; policy rules can match on them. |
Enrollment generates a fresh keypair on the device, proves possession by signing the token, and
stores the returned recorder credential in identity/recorder.token — never in config.json.
Exit code: 0 on success, 30 on any failure (unreachable Console, spent token, rejected proof).
sealr proxy mcp
A stdio MCP proxy. It sits between your agent and an MCP server, records every tool call, and — in enforce mode — refuses blocked calls without forwarding them.
sealr proxy mcp --name my-db --resource db:prod-main -- npx -y @modelcontextprotocol/server-postgres postgres://...| Flag | Default | What it does |
|---|---|---|
--enforce | off (observe) | Apply policy decisions. Without it every call is recorded and forwarded. Also switches the spool to fsync-per-write. |
--name <NAME> | mcp | Logical name for the wrapped server, used in records. |
--resource <RESOURCE> | none | Resource hint for policy binding, e.g. db:prod-main. Without it the target resource falls back to a tool-derived string and resource-bound rules will not match. |
-- <COMMAND...> | required | The MCP server command to wrap. Everything after --. |
A blocked call gets a protocol-correct JSON-RPC error synthesized back to the agent; the request is never written to the server's stdin.
Exit code: the wrapped server's exit code (0 if it reports none). A block does not change the proxy's exit code — the proxy keeps running.
sealr hook pre-tool-use
Claude Code PreToolUse hook. Reads the hook JSON on stdin, writes a decision JSON on stdout.
sealr hook pre-tool-use --enforce| Flag | Default | What it does |
|---|---|---|
--enforce | off (observe) | Return deny when policy blocks. Without it the operation is recorded and always allowed. |
Output is one JSON object:
{"decision":"allow"}{"decision":"deny","reason":"Blocked by Sealr: SQL_UNBOUNDED_WRITE"}The reason names the guard's first reason code. Only a BLOCK decision produces deny —
REQUIRE_APPROVAL on this surface allows and records, because the hook has nowhere to hold the
operation while a human decides.
Exit code: 0, including when the decision is deny (the denial is in the JSON, not the exit status).
30 if stdin is not valid JSON or the recorder is not initialized. The spool is fsync'd per write on
this surface regardless of --enforce.
sealr hook post-tool-use
sealr hook post-tool-useNo flags. Reads stdin and exits 0, tolerating any payload.
This command is a working stub. It accepts and discards the payload — it does not currently record an outcome record. Wire it into your agent config now if you want the hook plumbing in place, but do not expect post-execution outcomes in your evidence stream yet.
sealr run
Wraps and records an arbitrary command.
sealr run -- psql -h prod-db -c "SELECT count(*) FROM users"
sealr run --enforce --resource db:prod-main -- psql -c "DELETE FROM users"| Flag | Default | What it does |
|---|---|---|
--enforce | off (observe) | Apply policy decisions before execution. Also switches the spool to fsync-per-write. |
--resource <RESOURCE> | none | Resource hint for policy binding. Without it the target resource is cmd:<program>. |
-- <COMMAND...> | required | The command to run. Everything after --. |
Guard evaluation happens only for commands the wrapper recognizes:
| Program | Recognized when | What the guard sees |
|---|---|---|
psql | -c, --command or --command= is present | The SQL string |
terraform, tofu | always | The full argv (plan JSON is not available at argv time) |
git | a push argument is present | The full argv |
Anything else is recorded as a generic execution — argv redacted for the projected operation, exit
code, duration and output digests — and is never guard-evaluated, so --enforce cannot block it.
Exit codes: the wrapped command's exit code; 20 if the operation was blocked in enforce mode (or
blocked by an approval decision); 30 on a recorder error before execution.
sealr status
sealr status --jsonNo flags of its own. Prints the home path, stream id, connected/offline mode, cached policy version
and whether it is stale, and the spool size in bytes. --json adds recorder_id, tenant_id,
sync_enabled and console_endpoint.
Exit code: 0, or 30 if config.json exists but cannot be parsed.
sealr doctor
Self-check over config, keys, and the spool.
sealr doctorChecks performed, in order: config present · identity key present · stream initialized · key loadable · spool dir writable. It also reports the active keystore backend and flags the file backend as a downgrade (ADR-010).
Exit code: 0 when every check passes, 30 when any check fails.
doctor does not contact the Console. The "connectivity" part of a self-check is not implemented; a
network problem shows up as a warning on the next run or proxy, not here.
sealr policy show
sealr policy show --jsonPrints the cached bundle's version, its staleness, and each policy's name with its rule and binding
counts. Reads policy/bundle.json in the recorder home; an unreadable or absent bundle prints
version 0 rather than failing. Exit code 0.
sealr policy test
Dry-runs a policy file against one sample operation. Nothing is recorded and no Console is contacted.
sealr policy test ./policy.yaml \
--integration sql \
--resource db:prod-main \
--input "DELETE FROM users"| Argument | Default | What it does |
|---|---|---|
<FILE> | required | Policy YAML file to evaluate. |
--input <TEXT> | required | The operation payload: SQL text, plan JSON, or a git argv line. |
--integration <NAME> | sql | One of sql, terraform, git. Any other value routes to the generic guard, which emits no reason codes. |
--resource <RESOURCE> | db:prod-main | The target resource the rule binds against. |
Output names the guard decision, the risk class, the reason codes, the final policy decision, the matched rule id and the mode.
Exit code: 0 whatever the verdict — a BLOCK result is printed, not signalled. Exit 30 if the file
cannot be read or the YAML does not parse. Assert on the --json output if you want this in CI.
sealr export
Assembles a .seal bundle from the local spool.
sealr export -o evidence.seal| Flag | Default | What it does |
|---|---|---|
-o, --out <PATH> | required | Output bundle path. |
--level <LEVEL> | full | Export level recorded in the manifest. metadata selects metadata level; any other value is treated as full, including a typo. |
--level metadata sets the level in the manifest. It does not strip anything on the way out — the
recorder always writes its full local records, and the projection to metadata level happens at
Console ingest. Records never contain payload literals in the first place; they carry salted
commitments.
The bundle carries the records, the durable checkpoint log, the certificate chain and the trusted
root when present. sealr export prints the manifest hash.
Exit code: 0, or 30 when there are no records to export or the output cannot be written.
sealr verify
Verifies a bundle in-process, using the same engine as sealr-verify.
sealr verify evidence.seal
sealr verify evidence.seal --root ./root.pem --json| Argument | Default | What it does |
|---|---|---|
<BUNDLE> | required | Path to the .seal bundle. |
--root <PEM> | bundle-pinned / embedded root | Trusted root certificate PEM override. |
Exit codes: 0 pass · 10 pass with findings · 11 fail · 30 if the bundle cannot be opened.
For auditing, prefer sealr-verify: it is the standalone binary, and it supports the
--console-key and --expect-high-water options that sealr verify does not.
sealr version
sealr versionPrints the version and a reminder that there is no self-update — releases are verified and installed
with cosign. Distinct from --version, which prints only the version string. Exit code 0.
sealr-verify
sealr-verify <BUNDLE> [OPTIONS]| Flag | Default | What it does |
|---|---|---|
<BUNDLE> | required | Path to the .seal bundle. |
--root <PEM> | bundle-pinned / embedded root | Trusted root certificate PEM. Overrides both the embedded root and the one pinned in the bundle. |
--console-key <PEM> | key from the bundle | Console verifying key (SPKI PEM) used to check countersignatures. |
--expect-high-water <STREAM_ID=SEQ> | none | Pin a stream's authoritative extent. Repeatable. |
--json | off | Emit the machine-readable JSON report instead of text. |
-h, --help / -V, --version | — | Help and version. |
--expect-high-water is the answer to tail truncation. A bundle alone cannot prove that trailing
records were not removed before it was exported. Fetch the authoritative sequence out of band from
GET /v1/streams/{id}/integrity (max_seq) and pass it here to make missing trailing records a hard
failure:
sealr-verify evidence.seal --expect-high-water 01HZY...=4821Exit codes: 0 pass · 10 pass with findings · 11 fail · 2 malformed --expect-high-water
value · 30 usage or config error (unreadable bundle, unreadable --root or --console-key).
Exit codes
| Code | Meaning | Which commands |
|---|---|---|
| 0 | Success, or verification passed | all |
| 10 | Verification passed with findings | verify, sealr-verify |
| 11 | Verification failed | verify, sealr-verify |
| 20 | Operation blocked by policy in enforce mode | run |
| 30 | Recorder error; or doctor had a failing check; or sealr-verify usage error | all |
| 2 | Malformed --expect-high-water argument | sealr-verify |
| other | The wrapped process's own exit code | run, proxy mcp |
Under sealr run and sealr proxy mcp the exit code you see is usually the wrapped process's own.
A wrapped command that itself exits 20 or 30 is indistinguishable from a Sealr block or a recorder
error by exit code alone. Use --json records or stderr (sealr: command BLOCKED by policy (...))
when you need certainty in a script.
Environment variables
| Variable | Read by | What it does |
|---|---|---|
SEALR_HOME | all commands | Recorder home directory. --home overrides it. |
SEALR_PRINCIPAL | run, proxy mcp | Human principal (e.g. an OIDC subject) attached to records as an attributed subject. |
SEALR_LOG | sealr, sealr-ledger | Tracing filter, default warn. Logs go to stderr so stdout stays clean for JSON-RPC and hook output. Try SEALR_LOG=info. |
SEALR_KEYSTORE_PASSPHRASE | keystore | When set and non-empty, the identity key at rest is protected by Argon2id over this passphrase instead of a host-derived key. See configuration. |
Subject attribution falls back through these when SEALR_PRINCIPAL is unset: GITHUB_ACTOR,
GITLAB_USER_LOGIN, CI_COMMIT_AUTHOR (recorded as CI-attributed), then USER or USERNAME
(recorded as inferred). With none of them, the record is unattributed and says so.