Warrant Manifest Specification
An open standard for declaring CLI tool capabilities.
warrant.manifest.v1 Public Beta CC0 1.0 — No rights reserved The Warrant Manifest Specification enables policy engines to make fine-grained access control decisions over any command-line tool — without cooperation from the tool author. Anyone can write a manifest for any tool. No helper libraries, no upstream changes, no permission needed.
Why a Specification?
Most CLI security tools are closed systems — their rules are hardcoded and only cover tools the authors anticipated. The Warrant Manifest Specification takes a different approach: a published, open standard that anyone can implement.
- Tool authors can publish manifests alongside their CLIs
- Security teams can write manifests for internal tools
- Community contributors can cover the long tail of CLI tools
- Other projects can build their own policy engines on top of the same manifest format
The specification is released under CC0 1.0 — no rights reserved. Implement freely.
Design Principles
- Declarative, not imperative. Manifests describe capabilities — they don't enforce policy. Policy is defined separately in warrants.
- No cooperation required. Anyone can write a manifest for any tool. No helper libraries, no upstream changes.
- Human-readable. TOML format, reviewable by security teams, versionable in git.
- Composable. Manifests are independent units. Bundles compose them into setup profiles.
The CLI Control Surface
The schema is derived from a systematic analysis of how CLI tools are controlled. Every mechanism through which a user or agent influences a tool's behaviour is a potential vector that a policy engine must understand.
| Category | Mechanisms | Manifest Coverage |
|---|---|---|
| Invocation syntax | Commands, subcommands, positional args, options/flags, option bundling, -- terminator | match, when_*_flags, args, options, flags, respect_option_terminator |
| External configuration | Environment variables, config files | strip_env, env |
| Data streams | stdin, stdout, stderr, pipes, redirection | Out of scope (shell-layer; handled by policy engine) |
| Interactive/runtime | TTY prompts, REPL, signals | Out of scope (runtime; not statically declarable) |
| Process integration | Exit codes, IPC, daemon sockets | Out of scope (process-level; addressed by warrant-box) |
The manifest focuses on invocation syntax and external configuration — the two categories where a static document can fully describe a tool's interface. The core contribution: scope extraction turns opaque CLI invocations into structured, auditable capability checks — bridging syntax to semantics.
See the full design rationale in the canonical specification.
Document Structure
A manifest has four sections:
[manifest] # Metadata — tool identity, version, licence [transforms] # Scope transform functions available in this manifest [tool_policy] # Tool-level security policy (env stripping, path restrictions) [[commands]] # Command capability declarations (one or more)
[manifest] — Metadata
| Field | Type | Required | Description |
|---|---|---|---|
schema | string | ✅ | Must be "warrant.manifest.v1" |
id | string | ✅ | Namespaced identifier (e.g. "warrant-sh/git") |
tool | string | ✅ | Binary name as invoked on the command line |
tool_version | string | ✅ | Semver constraint (e.g. ">=2.30", "*") |
manifest_version | string | ✅ | Semver version of this manifest |
summary | string | Short description | |
license | string | SPDX licence identifier | |
source | string | URL to the upstream tool |
Namespaces
Manifest IDs use a namespace/name convention:
warrant-sh/*— maintained by the Warrant project<org>/*— maintained by a third-party organisation<user>/*— maintained by an individual
[[commands]] — Capability Declarations
Each entry maps a subcommand pattern to a named capability:
[[commands]]
match = ["push"]
when_no_flags = ["--force", "-f", "--force-with-lease"]
capability = "git.push"
description = "Push local commits to a remote repository"
risk = "moderate"
default = "allow"
args = { remote = 1, branch = 2 }
scope_examples = { remote = ["origin", "upstream"], branch = ["main", "release/*"] }
scope_descriptions = { remote = "Which remote to push to", branch = "Which branches" } Matching
| Field | Type | Description |
|---|---|---|
match | array | Subcommand tokens to match (e.g. ["push"], ["remote", "add"]) |
when_any_flags | array | Match only when at least one flag is present |
when_all_flags | array | Match only when all flags are present |
when_no_flags | array | Match only when none of these flags are present |
Flag-based specialisation enables capability splitting. The same subcommand can map to different capabilities depending on flags — e.g. git push (moderate risk) vs git push --force (high risk, separate capability).
Capability Metadata
| Field | Type | Description |
|---|---|---|
capability | string | Dotted capability name (e.g. "git.push") |
description | string | What this capability allows |
risk | string | "low", "moderate", "high", or "critical" |
default | string | Suggested default: "allow" or "deny" |
Scope Extraction
Scopes let policy engines control not just what command runs, but what it targets. Scopes are extracted from arguments, flags, options, or environment variables and passed through transform functions:
| Transform | Description | Example |
|---|---|---|
literal | Use value unchanged | "origin" → "origin" |
path | Canonicalise file path | "./src" → "/home/user/project/src" |
hostname | Extract hostname from URL | "https://api.example.com" → "api.example.com" |
email_domain | Extract domain from an email address | "ops@example.com" → "example.com" |
glob | Normalize value for glob pattern matching | "src/**" → "src/**" |
git_remote | Normalise git remote URL | "git@github.com:org/repo.git" → "github.com:org/repo" |
[tool_policy] — Tool-Level Security
| Field | Type | Description |
|---|---|---|
strip_env | array | Environment variables to remove before execution (supports * glob) |
paths | array | Allowed resolved binary paths (glob patterns) |
package_policy | string | Package security: "open", "denylist", or "allowlist" |
Registry Protocol
Manifests are distributed via a registry — a Git repository containing manifest files and a discovery index (registry.toml). The default registry is warrant-sh/registry on GitHub.
# Fetch protocol 1. Client fetches <registry_url>/registry.toml 2. Client compares sha256 hashes against local cache 3. Client downloads changed manifests 4. Client validates against this specification 5. Client writes to local cache
The registry URL is configurable via WSH_REGISTRY_URL.
Bundle Format
Bundles compose multiple manifests into named setup profiles:
[bundle] name = "codex" description = "Coding agents (Codex, Claude Code, OpenCode)" version = "1.0.0" [setup] shell_guard = true prompt_lock = true [[agents]] name = "codex" alias = "codex" [manifests] include = [ "warrant-sh/coreutils", "warrant-sh/git", "warrant-sh/cargo", "warrant-sh/npm", "warrant-sh/dangerous-patterns", "warrant-sh/sanitize-env", ]
From Manifest to Policy
The manifest is a factual capability map. Policy is defined separately:
manifest (factual) → draft (opinion) → warrant (signed policy) "git push exists" "allow push to origin" cryptographically signed
wsh pull git— fetch manifest from registrywsh add git— generate draft (every capability starts asreview)wsh edit git— set each capability toallowordenywsh lock— compile drafts into a signed warrant
This separation ensures that capability discovery is decoupled from capability authorisation. The same manifest can produce vastly different policies depending on the operator's requirements.
Manifest Authoring Guide
To write a custom manifest for a new CLI, follow this workflow:
- Identify the capability surface. List distinct actions and group by risk (read, write, destructive).
- Map invocation patterns. Use
matchand conditional flags to split capabilities when intent changes. - Define scopes. Extract target context (host, path, package, branch, remote) so policy can be precise.
- Choose transforms. Use
literal,hostname,path,git_remote, and other transforms as needed. - Validate and iterate. Run test invocations through
wsh check ...and confirm expected capability matches.
Registry Coverage (Reference)
The default registry currently includes manifests for:
- git — clone/fetch/push flows, including separate force-push capability
- cargo — build/test/run/publish/install and related workflows
- npm — install/run/publish/login/audit with package and script scopes
- curl — request/upload/write split by flags and output behavior
- docker — build/pull/push/run/exec/compose operations
- ssh — connect/scp/sftp behaviors
- pip — install/uninstall/freeze/download operations
Reference Implementation
- Parser:
warrant-shell/src/manifest.rs - Registry:
warrant-sh/registry - Policy engine:
warrant-core - Full specification:
spec/manifest-v1.md