# cilock CLI reference

Source: https://www.testifysec.com/docs/cilock/reference/cli

Source of truth: rookery/cilock/cmd/cilock/main.go and rookery/cilock/cli/. Defaults and flag names below track cilock as built from main, and are completeness-gated against that binary in CI (scripts/check-cli-coverage.mjs) — every command has a section here. A command that has not shipped in a release yet says so in its section; check cilock version before you rely on it.

> Source of truth: [`rookery/cilock/cmd/cilock/main.go`](https://github.com/aflock-ai/rookery/blob/main/cilock/cmd/cilock/main.go) and [`rookery/cilock/cli/`](https://github.com/aflock-ai/rookery/tree/main/cilock/cli). Defaults and flag names below track `cilock` as built from `main`, and are completeness-gated against that binary in CI (`scripts/check-cli-coverage.mjs`) — every command has a section here. A command that has not shipped in a release yet says so in its section; check `cilock version` before you rely on it.

 

```text
cilock - Collect and verify attestations about your build environments
```

 

CI/lock attestation types use the `https://aflock.ai/attestations/<name>/v0.1` namespace. Witness-style URLs (`https://witness.dev/attestations/<name>/v0.1`) are accepted via legacy aliases for interop with witness-produced evidence.

 

## Top-level commands

 

| Command | Purpose |
| --- | --- |
| `cilock login` | Sign in to the platform and store a session bound to a working tenant + product. |
| `cilock use` | Switch the working tenant/product the stored session binds attestations to. |
| `cilock whoami` | Show the current platform session (tenant, product, expiry). |
| `cilock logout` | Remove the stored platform session credential. |
| `cilock enroll agent` | Enroll this machine's agent principal in one ceremony: a human approves in the browser with a fresh passkey step-up, cilock stores and redeems the credential. |
| `cilock agent login` / `logout` / `status` / `list` / `remove` / `migrate` | Manage this machine's enrolled agent principals (sign in place of the human session when present). |
| `cilock trust [provider] [owner/repo]` | Register an OIDC identity the platform trusts for keyless upload (CI). |
| `cilock doctor` | Read-only preflight: is the environment sane to attest + upload against the platform? |
| `cilock git configure` | Configure Git commit and tag signing through CI/lock's standard X.509 signing protocol. |
| `cilock pushgate status` | Show whether the exact current ref and commit were accepted and delivered by Pushgate. |
| `cilock run [cmd]` | Run a command and record signed attestations about its execution. |
| `cilock attest` | Record attestations without wrapping a command (sugar for `run -- true`; for consultative/at-rest attestors). |
| `cilock attest vex` | Author a signed OpenVEX document from a triage decision (validated against the spec before it is signed). |
| `cilock sign [file]` | Sign an arbitrary file (typically a policy) with the configured signer. |
| `cilock verify` | Verify an artifact (subject) against a signed policy using attestations as evidence. |
| `cilock verify-bundle` | Verify a Sigstore bundle (`application/vnd.dev.sigstore.bundle*`) under the Sigstore client verification procedure. |
| `cilock policy from-bundles` | Generate a starter Witness policy from one or more signed attestation bundles. |
| `cilock policy from-commit` | Author a Witness policy from a commit's CI attestations already in the platform's Archivista. |
| `cilock policy push` | Upload a signed policy DSSE to the platform and create a release. |
| `cilock policy bind` | Bind a published policy definition/release to a product on the platform. |
| `cilock policy validate` | Validate a Witness/cilock policy document (schema; signature only with `-k` or `--require-signed`). |
| `cilock policy draft` | Hydrate a hand-authored policy with the tenant's platform trust roots. Returns it UNSIGNED. |
| `cilock keyid` | Print the canonical keyid (`hex(sha256(PEM(pub)))`) derived from a public or private key. |
| `cilock bundle create` / `inspect` | Build or inspect a portable attestation bundle (tar.gz of DSSE envelopes). |
| `cilock fetch <gitoid>` | Download any attestation from Archivista by its gitoid, verified against that content address. Added after 4.4.0. |
| `cilock plan -- <cmd>` | Show which attestors detection would fire for a command, without executing it. |
| `cilock attestors list` | List every attestor compiled into the binary. |
| `cilock attestors schema <name>` | Print the JSON schema of a specific attestor's predicate. |
| `cilock tools list` / `show` / `test-plan` | List supported detectors, show one, or emit per-tool test plans. |
| `cilock skill install` / `path` / `show` | Install, locate or print the Pushgate skill embedded in this binary for a coding agent. |
| `cilock get <tool>` | Install a trusted tool only if its release artifact matches the SHA-256 pin embedded in this binary. |
| `cilock completion <shell>` | Emit shell completion script (bash, zsh, fish, powershell). |
| `cilock version` | Print the `cilock` version (`--json` for `version`, `commit`, `built`, `embedded_trust`). |

 

## Global flags

 

These persistent flags are accepted on every subcommand:

 

| Flag | Default | Notes |
| --- | --- | --- |
| `--log-level, -l <level>` | `info` | One of `debug`, `info`, `warn`, `error`. |
| `--debug-cpu-profile-file <path>` | (none) | Write a CPU pprof profile to this path. Profiling enabled when non-empty. |
| `--debug-mem-profile-file <path>` | (none) | Write a heap pprof profile to this path. Profiling enabled when non-empty. |
| `--policy-hardening <mode>` | `enforce` | Policy-verification hardening: `enforce` rejects dangerous policy configurations; `warn` downgrades them to loud warnings. Also settable via `CILOCK_POLICY_HARDENING`. |

 

## Platform session & CI trust

 

These commands establish and inspect the platform session that attestation **upload** (and keyless signing-token exchange) need. Signing itself is keyless and needs no login; uploading to Archivista binds the evidence to your tenant/product, which is what the session carries. The onboarding path is `login` → (`use` to switch scope) → `trust` to let CI upload → `doctor` to preflight.

 

The platform is derived from a single `--platform-url` (default `https://platform.testifysec.com`); it auto-resolves Fulcio, TSA, and Archivista from that host's discovery document. After login, the commands that *consume* a session — `run`, `trust`, `bundle`, `policy …`, `pushgate status` — default to the platform you logged into. The commands that *manage* sessions (`use`, `whoami`, `logout`, and `agent login`/`logout`/`status`) do not: with no flag they act on `https://platform.testifysec.com`, so against any other platform pass the same `--platform-url` you gave `login` to every one of them.

 

### `cilock login`

 

Sign in and store a session credential. The browser approve page binds a working **tenant AND product** — creating a default tenant/product if you have none — so every subsequent attestation is scoped to one. Identity resolves by precedence: `--token` (explicit JWT, CI/headless; `-` reads stdin) → ambient CI workflow OIDC (GitHub Actions, auto-detected) → interactive browser (default for local use).

 

| Flag | Default | Description |
| --- | --- | --- |
| `--platform-url <url>` | `https://platform.testifysec.com` | Platform to sign in to. |
| `--token <jwt>` | (none) | JWT for CI/headless login (skips the browser); `-` reads it from stdin. |
| `--workflow-identity` | `false` | Use the ambient CI workflow OIDC identity (auto-detected on the default platform; **required** to send a workflow token to a non-default `--platform-url`). |
| `--interactive` | `false` | Force the interactive browser login (skip ambient CI identity). |
| `--tenant <id\|name>` / `--product <id\|name>` | (none) | Pre-select tenant/product on the approve page. |
| `--tenant-id <uuid>` / `--product-id <uuid>` | (none) | Bind tenant/product directly for a headless `--token` login. |
| `--tenant-name` / `--product-name <str>` | (none) | Label to record alongside `--tenant-id` / `--product-id`. |
| `--allow-trust` | `false` | Also grant the narrow `oidc:write` scope so this session can run [`cilock trust`](https://www.testifysec.com/docs/cilock/reference/cli#cilock-trust). Off by default. |
| `--no-browser` | `false` | Never open a browser: fail at once with the headless options instead. |

 

In CI (`CI=true` or a detected CI provider) `cilock login` never opens a browser, `--interactive` included: it fails immediately and names the headless options instead of waiting five minutes for a callback nobody will make.

 

```bash
# Interactive browser login (binds tenant+product on the approve page)
cilock login

# CI on GitHub Actions: ambient workflow identity (needs permissions: id-token: write)
cilock login --workflow-identity --platform-url "$PLATFORM_URL"

# CI/headless with an explicit JWT + the tenant+product to bind
cilock login --platform-url https://platform.example.com --token "$TESTIFYSEC_TOKEN" \
  --tenant-id <uuid> --product-id <uuid>
```

 

### `cilock use`

 

Switch the working tenant + product the stored session binds attestations to, so `cilock run` scopes evidence without re-prompting. Requires an existing session (`cilock login` first). The analog of `kubectl config use-context` for cilock.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--product-id <uuid>` / `--tenant-id <uuid>` | (none) | Bind directly (no browser). |
| `--product-name` / `--tenant-name <str>` | (none) | Label recorded alongside the id. |
| `--product <id\|name>` / `--tenant <id\|name>` | (none) | Select by name on the approve page (re-opens the browser to resolve names → ids, auto-creating a default tenant/product if you have none). |
| `--platform-url <url>` | `https://platform.testifysec.com` | Platform whose session to rebind. Must match the `login`. |
| `--no-browser` | `false` | Never open a browser; only `--product-id` / `--tenant-id` bind. Also the behaviour in CI with no terminal. |

 

```bash
# Switch the working product by id (no browser)
cilock use --product-id 5664d4f5-9003-41e8-90e4-035c51d09b45 --product-name acme-web

# Pick or create tenant+product interactively
cilock use

# Pre-select by name on the approve page
cilock use --tenant acme --product acme-web
```

 

### `cilock whoami`

 

Show the platform session — the logged-in tenant, bound product, and expiry — for one platform.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--platform-url <url>` | `https://platform.testifysec.com` | Platform whose session to show. Must match the `login`. |
| `--json` | `false` | Emit one JSON object (`logged_in`, `platform_url`, `session`, `auth_mode`, tenant, product, `email`, `expires_at`, `agent_id`). With no session it prints `"logged_in": false` and exits non-zero, like the text form. |

 

```bash
cilock whoami
cilock whoami --platform-url https://platform.example.com
cilock whoami --json
```

 

### `cilock logout`

 

Remove the stored platform session credential for one platform. It does not look up which platform you logged into: with no flag it removes the public-platform session, and a session on any other platform stays on disk while the command exits 0.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--platform-url <url>` | `https://platform.testifysec.com` | Platform whose session to remove. Must match the `login`. |

 

```bash
cilock logout
cilock logout --platform-url https://platform.example.com
```

 

### `cilock enroll agent`

 

Enroll an agent principal for this machine in one ceremony. A browser window opens on the platform's enrollment page; **your human** signs in, reviews what is being minted — name, organization, repository scope, lifetime — steps up with their passkey (required afresh for every ceremony — a session that stepped up this morning is not a person present now), and confirms. The passkey locks in exactly what was reviewed: the platform digests the review into the ceremony's challenge and refuses a mint that differs in any of those fields, so a browser an agent drives cannot take the gesture for one enrollment and submit another. The platform mints the principal inside that session and hands the one-time credential, sealed to this ceremony's ephemeral key, straight back to the command, which stores it `0600` and then **redeems it**. Only that first exchange activates the principal. The delivered credential is held *pending* beside whatever this machine already signs with, never over it: if the platform refuses it (its own structured verdict — a bare 401/403 from a proxy is not one), the command fails and discards only the pending credential, and the previously enrolled identity keeps signing; if the platform did not answer, both are kept and the next `cilock run` redeems the pending one first. A ceremony that did not complete leaves nothing that claims to be an identity, and costs nothing that was.

 

The identity is **time-bound**: it stops signing at the lifetime the human confirmed (eight hours by default, seven days at most) and cannot be extended. Run the command again for a new principal. An agent may run this command; it cannot complete it — nothing on the command line substitutes for the person in the browser.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--platform-url <url>` | `https://platform.testifysec.com` | Platform to enroll with. |
| `--name <label>` | (none) | Pre-fill the principal's display label on the approve page. |
| `--repo <owner/name>` | (none) | Pre-select a repository scope on the approve page. |
| `--ttl <duration>` | platform default (8h) | Pre-select the principal's lifetime on the approve page; 15m–168h. |

 

```bash
cilock enroll agent
cilock enroll agent --name claude-on-dev-laptop --repo acme/widget --ttl 4h
```

 

`cilock agent enroll` is a hidden alias of the same command.

 

### `cilock agent login`

 

Store a refresh credential minted for this agent outside the `enroll agent` ceremony (for example by an administrator running the `createAgentPrincipal` mutation directly). The credential is read from **STDIN by default** so it never lands in shell history or a process listing; it is written `0600` to cilock's own agent store, kept apart from the `cilock login` session, and never printed again. Once stored, runs against that platform sign as the **agent principal**, taking precedence over any human `cilock login` session. The tenant and agent ids are not secret — they are the SPIFFE path segments (`spiffe://<trust-domain>/tenant/<tenant-id>/agent/<agent-id>`) every certificate this credential buys will carry.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--platform-url <url>` | `https://platform.testifysec.com` | Platform the agent is enrolled with. |
| `--tenant-id <uuid>` | (none) | Tenant UUID this agent is enrolled in (SPIFFE path segment). |
| `--agent-id <uuid>` | (none) | Agent principal UUID minted at enrollment (SPIFFE path segment). |

 

```bash
# Read the credential from stdin (preferred)
cilock agent login --platform-url https://platform.example.com \
  --tenant-id <uuid> --agent-id <uuid> < credential.txt
```

 

### `cilock agent logout`

 

Remove **all** of this machine's local agent credentials for the platform, active and pending. To remove one agent and keep the others, use `cilock agent remove <agent-id>`. This is a **local delete, not a revocation**: the principal stays valid on the platform until a human revokes it there. The credential is keyed by platform, and `logout` does not look up which platform you enrolled with — it removes the credential for `--platform-url`, defaulting to the public platform. Pass the same `--platform-url` you gave `cilock agent login`, or a non-default enrollment stays on disk while the command prints `No agent credential stored for …` and exits 0.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--platform-url <url>` | `https://platform.testifysec.com` | Platform whose agent credential to remove. Must match the `login`. |

 

```bash
cilock agent logout --platform-url https://platform.example.com
```

 

### `cilock agent status`

 

Show the agent principal this machine would sign as against one platform: the full SPIFFE ID once the platform has redeemed the credential, or `not yet redeemed` while it has not (an unredeemed credential is kept across a transient refusal and the next `cilock run` retries), and when its authority expires. Like `logout`, it reads the credential for `--platform-url` only; with no flag it reports the public platform, so a non-default enrollment shows as absent unless you name it.

 

An **expired** credential prints `EXPIRED` and **exits non-zero**, so a script can gate on a dead identity without parsing the output. There are two ways out and cilock takes neither on its own: `cilock enroll agent` for a new ceremony, or `cilock agent logout` to sign as your own session. An enrolled agent credential pre-empts the human session machine-wide by design — falling back to it silently would put a human's name on an agent's work — so `cilock run` refuses the same way, **before** it executes the wrapped command, rather than spending the build on a refusal it could read locally.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--platform-url <url>` | `https://platform.testifysec.com` | Platform to report the enrolled agent for. Must match the `login`. |

 

```bash
cilock agent status --platform-url https://platform.example.com
```

 

### `cilock agent migrate`

 

Rewrite this machine's agent credential store as version 2, which holds several agents per platform. Version 1 holds one: enrolling a different agent while the first is live is refused there (`cilock agent remove <id>` the old one, or migrate). No other command changes the format. Reads never rewrite the file, and a new store is created as version 1.

 

Upgrade every local cilock first (the installed binary git signs with, jade, mint workers). An older cilock cannot read version 2 and refuses to sign; it never falls back to your human session. No credential changes, and running it again does nothing.

 

```bash
cilock agent migrate
```

 

### `cilock agent list`

 

List every local agent credential for one platform: agent id, tenant, whether it is active or pending (delivered, not yet redeemed), its recorded repository scope with the time the platform answered it, and its expiry. Bearers are never printed. These are local records only: the platform is not checked, so a revoked principal still appears.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--platform-url <url>` | `https://platform.testifysec.com` | Platform whose local agents to list. |

 

```bash
cilock agent list --platform-url https://platform.example.com
```

 

### `cilock agent remove`

 

Remove one agent's local credentials, active and pending, for the platform. Every other agent stays. This is a **local delete, not a revocation**: the principal stays valid on the platform until a human revokes it there. On a version 1 store it is how you make room to enroll a different agent.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--platform-url <url>` | `https://platform.testifysec.com` | Platform the agent is enrolled with. |

 

```bash
cilock agent remove 22222222-2222-2222-2222-222222222222
```

 

### `cilock trust`

 

Register an OIDC **federated** identity the platform will trust for keyless attestation upload — the CI complement to [`cilock run`](https://www.testifysec.com/docs/cilock/reference/cli#cilock-run-cmd). It creates an OIDC credential only; cilock never mints a long-lived API-token secret. Run it as a tenant admin after `cilock login --allow-trust` (the `oidc:write` scope is opt-in). The audience defaults to the same `${platform}/archivista` that `cilock run` uploads to, and the subject is templated from the provider's claim convention, so trust and run can't drift. Providers: `github`, `gitlab` (or `--issuer` + `--subject` for any other); on-prem GHES / self-hosted GitLab add `--host`.

 

| Flag | Default | Description |
| --- | --- | --- |
| `[provider] [owner/repo]` | auto-detect repo | Positional: e.g. `github acme/widget`. With no args (interactive), detects the current repo. |
| `--host <host>` | (none) | On-prem instance host for the provider (e.g. `github.acme.com`). |
| `--issuer <url>` / `--subject <glob>` | (none) | Generic provider escape hatch (use together). |
| `--audience <aud>` | `${platform-url}/archivista` | OIDC audience (matches `cilock run`). |
| `--scope <s>` | `attestation:upload` | Repeatable. Only `attestation:{upload,read,verify}` allowed. |
| `--verify` | `false` | Also grant `attestation:read` (for `cilock verify --enable-archivista`). |
| `--allowed-ip <cidr>` | any | Source IP/CIDR allowlist (repeatable; e.g. the runner egress). |
| `--name` / `--description <str>` | `<provider>:<slug>` | Credential name / description. |
| `--tag <t>` | (none) | Categorization tag (repeatable). |
| `--tenant <id>` | logged-in working tenant | Tenant to register the trust under. |
| `--dry-run` | `false` | Print what would be created without calling the platform. |
| `--yes, -y` | `false` | Skip the interactive confirmation. |

 

```bash
# Trust a GitHub repo's Actions to upload (most common)
cilock trust github acme/widget

# Interactive: auto-detect the current repo and confirm
cilock trust

# On-prem GitHub Enterprise Server
cilock trust github acme/app --host github.acme.com

# Any OIDC provider (generic escape hatch)
cilock trust --issuer https://oidc.corp/foo --subject sub:acme:prod
```

 

### `cilock doctor`

 

Read-only preflight (no build, no upload) of a cilock attestation environment. Prints a green/red checklist: logged in? platform reachable (`.well-known/judge-configuration` discovery)? Fulcio / TSA / Archivista destinations (derived + discovered); upload authorization (login session origin matches Archivista origin). Run it before a multi-minute `cilock run` to confirm signing + upload will work. `--json` emits a machine-readable report an agent can gate on (`report.ok`).

 

| Flag | Default | Description |
| --- | --- | --- |
| `--platform-url <url>` | `https://platform.testifysec.com` | Platform to probe. |
| `--json` | `false` | Emit the preflight report as a single JSON object (`report.ok` is the rollup to gate on). |

 

```bash
# Check the default hosted platform
cilock doctor

# Check a self-hosted / standalone platform, machine-readable
cilock doctor --platform-url https://judge.example.com --json
```

 

## `cilock git configure`

 

Configure Git's standard X.509 signing protocol to use CI/lock for signed commits and tags. By default the command changes only the current repository; `--global` applies the same configuration to every repository for the current user. CI/lock obtains a short-lived Fulcio certificate from the selected TestifySec platform and requires its RFC 3161 timestamp authority, so no long-lived signing key is written into the repository.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--global` | `false` | Write the signing configuration to the current user's global Git configuration instead of this repository. |

 

```bash
# Configure this repository, then use ordinary Git signing commands
cilock git configure
git commit -S -m "feat: signed by CI/lock"
git verify-commit HEAD

# Configure all repositories for this user
cilock git configure --global
```

 

## `cilock pushgate status`

 

Read the exact Pushgate delivery record for a Git ref and commit. With no flags, CI/lock discovers the current branch, `HEAD` commit, selected platform, and the configured Pushgate Git remote. The platform discovery document pins the trusted Pushgate origin; the request uses the matched remote's repository-scoped credential and refuses redirects rather than forwarding that credential.

 

Use `--wait` after `git push` to stay attached until delivery finishes. A delivered push exits successfully; a refusal, conflict, terminal delivery failure, or timeout exits non-zero with an actionable status. `--json` emits one machine-readable object when the command finishes.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--remote <name>` | inferred Pushgate remote | Select a Git remote explicitly when more than one matches the discovered Pushgate origin. |
| `--ref <ref>` | current branch | Fully qualified `refs/heads/*` or `refs/tags/*` ref. Discovery reads `git symbolic-ref HEAD`, which resolves a branch only, so a checked-out tag — a detached `HEAD` — must pass `--ref` explicitly. |
| `--commit <sha>` | `HEAD` | Exact lowercase 40-character commit to query. |
| `--wait` | `false` | Poll until delivery succeeds or reaches a terminal failure. |
| `--timeout <duration>` | `15m` | Maximum time to wait for delivery. |
| `--json` | `false` | Emit the final status or stable error as JSON. |

 

```bash
# Inspect the current commit without changing anything
cilock pushgate status

# Push, then remain attached through asynchronous Git-provider delivery
git push pushgate HEAD
cilock pushgate status --wait

# Agent-friendly output with an explicit wait budget
cilock pushgate status --wait --timeout 20m --json
```

 

## `cilock run [cmd]`

 

> Runs the provided command and records attestations about the execution.

 

Always-run attestors (cannot be omitted): `material`, `product`, and (when args are provided) `command-run`. Trying to pass `command-run` via `--attestations` is rejected.

 

Only **one signer** is supported per `run` invocation (enforced in `cilock/cli/signer_error.go`).

 

### Common flags

 

| Flag | Short | Default | Description |
| --- | --- | --- | --- |
| `--step <name>` | `-s` | inferred | Step category. Optional — when omitted, [inferred from the wrapped command](https://www.testifysec.com/docs/cilock/concepts/step-categories). Must be a value from the step lexicon. |
| `--attestations <list>` | `-a` | `environment,git,platform` | **Comma-separated** attestors (`product` + `material` are always recorded). Passing `-a` disables [auto-detection](https://www.testifysec.com/docs/cilock/concepts/auto-detection-and-defaults) (set becomes exact) unless `--workload auto`. |
| `--workingdir <dir>` | `-d` | current dir | Working directory for material/product capture. |
| `--outfile <path>` | `-o` | stdout | Path for the signed DSSE envelope. |
| `--trace` | `-r` | `false` | Enable syscall tracing (Linux). Backend is ptrace+seccomp or eBPF — see [capture modes](https://www.testifysec.com/docs/cilock/concepts/capture-modes). No-op on non-Linux. |
| `--ignore-command-exit-code` | (none) | `false` | Exit 0 from cilock even when the wrapped command exits non-zero. The exit code is recorded and signed in `command-run/v0.2` either way (and the envelope is written and uploaded either way), so a policy rule on `input.exitcode` can deny the run; without the flag a non-zero exit also fails `cilock run` itself. Useful for tools that signal findings via exit code (e.g. `oscap` exits 2, scanners exit 1). |
| `--hashes <list>` | (none) | `sha256` | Hash algorithms used in digests (comma-separated). |
| `--max-attestation-bytes <size>` | (none) | `4MiB` | Largest in-toto statement CI/lock will sign, as plain bytes (`4194304`) or with a unit (`4MiB`, `512KiB`, `4MB` — binary units are 1024-based, `KB`/`MB`/`GB` are 1000-based). A statement over this is refused **before** it is signed, written or uploaded, and the error names the total, the limit and the five largest attestors with a remedy for each. Also settable via `CILOCK_MAX_ATTESTATION_BYTES` (the flag wins). `0` disables the limit and warns once on stderr. See [why 4 MiB](https://www.testifysec.com/docs/cilock/reference/cli#why-the-attestation-size-limit-is-4-mib). |
| `--dirhash-glob <list>` | (none) | (none) | Globs for which directories should be hashed as a single unit. |
| `--timestamp-servers <list>` | `-t` | (none) | RFC 3161 TSA URLs (comma-separated; repeatable). |
| `--enable-archivista` | (none) | `false` | Push the signed envelope to Archivista. |
| `--archivista-server <url>` | (none) | `https://platform.testifysec.com/archivista` | Archivista server URL (derived from `--platform-url` if not explicitly set). |
| `--archivista-headers <h>` | (none) | (none) | Repeatable `Authorization: ...` headers for Archivista. |
| `--archivista-oidc` | (none) | `false` | Use GitHub Actions OIDC for Archivista auth (auto-enabled in GitHub Actions). |
| `--archivista-audience <aud>` | (none) | Archivista server URL | OIDC audience claim. |
| `--platform-url <url>` | (none) | `https://platform.testifysec.com` | TestifySec platform URL; archivista, fulcio, and TSA URLs are derived from it if unset. |
| `--env-filter-sensitive-vars` | (none) | `false` | Remove sensitive env vars from output rather than obfuscating. |
| `--env-add-sensitive-key <key>` | (none) | (none) | Add a name or glob (e.g. `*TOKEN*`) to the sensitive env list (repeatable). |
| `--env-allow-sensitive-key <key>` | (none) | (none) | Whitelist a specific key from the sensitive list. |
| `--env-disable-default-sensitive-vars` | (none) | `false` | Disable CI/lock's default sensitive-var list entirely. |

 

### Capture, detection & hardening flags

 

| Flag | Default | Description |
| --- | --- | --- |
| `--capture-mode <mode>` | `auto` | Where material/product digests come from: `auto` (trace if `--trace`, else walk), `walk`, `trace` (requires `--trace`), `ima`. See [capture modes](https://www.testifysec.com/docs/cilock/concepts/capture-modes). |
| `--hardening <profile>` | `standard` | Integrity profile: `off`, `standard` (fanotify on, fs-verity opportunistic), `strict` (fanotify + fs-verity required, drops fail). |
| `--require-zero-drops` | from `--hardening` | Fail the run if the trace dropped any event. `strict` ⇒ `true`. |
| `--workload <mode>` | `auto` | Attestor selection. `auto` detects (only when `-a` absent, unless forced); `manual` uses `-a`/defaults exactly. See [auto-detection](https://www.testifysec.com/docs/cilock/concepts/auto-detection-and-defaults). |
| `--validate-only` | `false` | Run pre-flight workload + tool checks, print the planned attestor set, exit without running the command. |
| `--no-default-attestor <name>` | (none) | Drop an always-on attestor (`product`, `material`). Repeatable. |
| `--material-manifest` | `false` | Publish the material attestor's per-file leaves as a detached companion envelope (`<outfile>-material-manifest.json`), stored before the collection when Archivista upload is on. The predicate records `manifestUploaded` and the manifest's content digest either way. The Merkle root is signed regardless — this changes only where the proof material lives. |
| `--diagnose` | `false` | Verbose internal logging (eBPF load, fanotify, ringbuf drops, fs-verity). Sets `CILOCK_DIAGNOSE=1`. |
| `--cache-add-pattern <glob>` | (none) | Add a glob to the build-cache classification set (cache files aren't products). Repeatable. |
| `--cache-allow-pattern <glob>` | (none) | Remove a glob from the cache set (treat as a product). Repeatable. |
| `--prewalk-skip-dir <name>` | (none) | Add a basename to the pre-trace walk skip list (defaults: `.git`, `node_modules`, `vendor`, `.cache`). Repeatable. |
| `--prewalk-include-dir <name>` | (none) | Force the pre-trace walk into a basename even if skipped. Most-specific wins. Repeatable. |

 

Capture backend selection within `--trace` is controlled by the `--capture-mode` suffix — `trace:auto` (eBPF, else ptrace), `trace:ebpf` (require eBPF), or `trace:ptrace` (skip the eBPF probe) — plus the `CILOCK_FANOTIFY` / `CILOCK_FSVERITY` feature toggles.

 

Plus the **signer flags** (see below) and **attestor-specific flags** prefixed `--attestor-<name>-*` (e.g. `--attestor-secretscan-fail-on-detection`, `--attestor-product-include-glob`).

 

### Signer selection

 

CI/lock loads signers based on which `--signer-*-*` flags are set. The **default binary** registers two signer providers plus a KMS provider:

 

- **`file`:** `--signer-file-key-path`, `--signer-file-cert-path`, `--signer-file-intermediate-paths`, `--signer-file-key-passphrase`, `--signer-file-key-passphrase-path`
 - **`fulcio`:** `--signer-fulcio-url`, `--signer-fulcio-oidc-issuer`, `--signer-fulcio-oidc-client-id`, `--signer-fulcio-oidc-redirect-url`, `--signer-fulcio-token`, `--signer-fulcio-token-path`, `--signer-fulcio-token-env` (GitLab id\_tokens variable, default `SIGSTORE_ID_TOKEN`), `--signer-fulcio-use-http` (default `true`)
 - **`kms`:** `--signer-kms-ref` (key reference URI, e.g. `awskms://`, `gcpkms://`, `azurekms://`, `hashivault://`), `--signer-kms-hashType` (default `sha256`), `--signer-kms-keyVersion`

 

Additional providers (`spiffe`, `vault`, and per-cloud KMS broker clients with their extra sub-flags) are **not** compiled into the default release binary; add them via a custom build — see [build a custom CI/lock](https://www.testifysec.com/docs/cilock/guides/build-a-custom-cilock). Run `cilock run --help-advanced` to see the exact signer flags your binary exposes.

 

For the full URI conventions, see [signing & identity](https://www.testifysec.com/docs/cilock/concepts/signing-and-identity).

 

## `cilock plan`

 

> Dry-run of [`cilock run`](https://www.testifysec.com/docs/cilock/reference/cli#cilock-run-cmd)'s pre-gate detection: prints which attestors **would fire** for a hypothetical command, which would be skipped (with reasons), and any warnings — **without executing** the command. Take the names from the `fire` list and pass them to `cilock run -a <attestor>,...` to run the planned set.

 

| Flag | Default | Description |
| --- | --- | --- |
| `-- <command> [args...]` | (required) | The command to plan for (after the `--` separator). |
| `--format <fmt>` | `text` | `text` or `json` (machine-readable, for an agent to consume). |
| `--verbose, -v` | `false` | Include the full skip list (every detector considered) in text output. |

 

```bash
# Show which attestors would fire for a build, without running it
cilock plan -- go build ./...

# Machine-readable plan for an agent to consume
cilock plan --format json -- docker build -t app .
```

 

## `cilock sign [file]`

 

> Signs a file with the provided key source and outputs the signed file to the specified destination.

 

Wraps an arbitrary file in a DSSE envelope. Used most commonly to sign a policy document before distribution. Same signer-selection model as `run`. Only one signer per invocation.

 

| Flag | Short | Default | Description |
| --- | --- | --- | --- |
| `--infile <path>` | `-f` | (required) | File to sign (typically the policy JSON). |
| `--outfile <path>` | `-o` | stdout | Destination for the signed DSSE envelope. |
| `--datatype <uri>` | `-t` | `https://witness.testifysec.com/policy/v0.1` | DSSE `payloadType`. Default is the witness policy type for backward compatibility; CI/lock also accepts `https://aflock.ai/policy/v0.1`. |
| `--max-attestation-bytes <size>` | (none) | `4MiB` | Largest input `sign` will wrap, measured on the bytes read from `--infile` — for `sign` the input *is* the payload, so nothing is framed around it. Refused before a signer is loaded, so an oversized file fails even with no key configured. Same grammar, env var and `0` opt-out as [`run`](https://www.testifysec.com/docs/cilock/reference/cli#cilock-run-cmd). |
| `--platform-url <url>` | (none) | `https://platform.testifysec.com` | Platform whose session is exchanged for a keyless Fulcio certificate. Pass `""` to sign with `--signer-*` only. |
| `--offline` | (none) | `false` | Alias for `--platform-url ""`, the same opt-out `run` and `verify` take: no session lookup, no keyless exchange, no platform TSA. Needs a local signer (`-k` or a `--signer-kms-*`/`--signer-vault-*`/`--signer-spiffe-*` provider); without one the command says so instead of failing with "no signers found". |

 

### Why the attestation size limit is 4 MiB

 

The number comes from what the platform can afford to *read*, not from what a signer can produce.

 

On each push evaluation the platform downloads and JSON-parses **every** envelope matching the commit, three times, and caches nothing above 512 KiB. Measured 2026-09-15 on the evaluate-release path: about **0.4 s per MB**. A commit with no large envelope evaluates in 0.95 s median; one 45 MB envelope takes 18.6 s; two take 32 s. A single evaluation is timed out at the edge after **25 s**, under Envoy's 30 s route timeout — so a pair of oversized envelopes does not merely make a push slow, it makes the platform report itself unreachable.

 

That is not hypothetical. A command-run attestor that captured a whole `go test -json` stream produced 45.6 MB and 47.1 MB envelopes and took pushes over the edge budget. A 4 MiB ceiling keeps a commit carrying several envelopes inside roughly two seconds of parse time. The edge has 4 MiB constants of its own, but they are not a per-envelope read cap and this limit is not derived from them: that number is the git push *prefix* cap (the ref-update section, push certificate and push-options), and the edge's whole-body cap is 16 MiB. The agreement is a coincidence, and the parse-time measurement above is the only thing this default rests on.

 

For scale on the other side: a real `push-tests` mint of this repository — `-a git -a alps-evidence` with the product exclude glob, compact evidence profile — measured **17,023 bytes** of statement on 2026-09-15. The default leaves about 240x headroom for ordinary evidence, so a run that trips it has a stdout-capture or a material/product-scope problem, which is what the refusal's per-attestor breakdown points at.

 

The margin is narrower on a **legacy**-profile build, where the material attestor's per-file leaves stay inline. The same repository measured a 4.95 MiB envelope — about 3.7 MiB of statement, 17,152 leaves — before compact inventories detached them, which is 88% of this limit rather than 0.4% of it. That is the intended behaviour: a legacy build really is producing the envelopes push evaluation struggles with. Raise the limit deliberately with the flag or the env var if you need it, or move to a compact build.

 

`verify` has no such flag, deliberately. The limit is a **mint-time** guardrail; evidence already signed — including envelopes minted before the limit existed — must stay verifiable forever.

 

Companion envelopes (`--material-manifest`, detached file inventories) are exempt. They are keyed by tree root and unreachable from a commit-keyed lookup, so a push evaluation never opens one, and each carries its own ceiling and its own upload consent.

 

## `cilock verify`

 

> Verifies an **artifact (subject)** against a signed policy. You name the subject — an artifact file (`-f`) or a digest such as `sha256:$DIGEST` (`-s`) — and CI/lock uses the supplied **attestations as the evidence** that validates it. You verify the thing, not the attestation.

 

Because v0.3 product/material attestations [inline their Merkle leaves](https://www.testifysec.com/docs/cilock/attestors/product) by default, `cilock verify <artifact> -p policy -a <attestations>` resolves the artifact's digest to its signed tree with **no separate inclusion-proof envelope** — the inclusion-proof bridge maps the artifact's sha256 to the `tree:products` root directly from the signed attestation's inline leaves.

 

**Policy-signer trust.** A build can embed its policy trust anchors (policy CA root, TSA root, signer functionary) at compile time, so `cilock verify <artifact> -p policy -a <attestations>` needs no `--policy-*` flags; verify prints the trust anchors it uses. The canonical binary ships **empty** embedded trust (`{}`), so you supply trust per dimension via `-k`/`--publickey` (key) or `--policy-ca-roots` + the Fulcio constraint flags below. Flags override embedded trust; verify **fails closed** when neither a flag nor embedded trust is present for a required dimension.

 

| Flag | Short | Description |
| --- | --- | --- |
| `--policy <path>` | `-p` | Path to the signed DSSE policy envelope. |
| `--publickey <path>` | `-k` | Path to the policy signer's public key (PEM). |
| `--attestations <list>` | `-a` | Attestation envelope files (comma-separated; repeatable). |
| `--artifactfile <path>` | `-f` | Path to the artifact subject to verify. |
| `--subjects <list>` | `-s` | Additional subjects to use when looking up attestations (e.g. `sha256:<hex>`). Useful when the artifact file itself isn't available locally — verify by a known digest instead. **Do not** pass `sha1:$COMMIT` to anchor a match: sha1-commit anchoring was rejected (chosen-prefix collision, CVE-2026-22703) — `--artifactfile` (see above) already resolves the multi-stage-pipeline product/material mismatch that anchoring on the commit hash used to work around, via the inline-leaf inclusion-proof bridge. |
| `--directory-path <path>` | (none) | Path to a directory subject (for material/product matching). |
| `--enable-archivista` + `--archivista-server` | (none) | Pull collections from Archivista by subject digest instead of (or in addition to) file paths. |
| `--policy-ca-roots <list>` | (none) | X.509 roots for verifying a policy signed via x.509 cert (replaces the deprecated `--policy-ca`). |
| `--policy-ca-intermediates <list>` | (none) | Intermediate CAs for the policy cert chain. |
| `--policy-commonname`, `--policy-dns-names`, `--policy-emails`, `--policy-organizations`, `--policy-uris` | (none) | Cert-constraint fields when the policy is signed with x.509. |
| `--policy-fulcio-oidc-issuer`, `--policy-fulcio-build-trigger`, `--policy-fulcio-build-config-uri`, `--policy-fulcio-runner-environment`, `--policy-fulcio-run-invocation-uri`, `--policy-fulcio-source-repository-{ref,identifier,digest}` | (none) | Fulcio cert-constraint fields pinning a **keyless** policy signer — e.g. `--policy-fulcio-build-config-uri https://github.com/org/repo/.github/workflows/release.yml@*` pins which workflow may sign a trusted policy without pinning the ref; `--policy-fulcio-runner-environment github-hosted`. |
| `--policy-timestamp-servers <list>` | (none) | Trusted TSA CA cert paths for verifying timestamped policies. |
| `--verifier-kms-*` | (none) | Same shape as `--signer-kms-*`, used when the policy's public key is referenced by a KMS URI. |
| `--offline` | (none) | Alias for `--platform-url ""`: fully offline verify. |
| `--format <fmt>` | (none) | `text` (default) or `json`, which also emits one machine-readable verdict object on stdout. An unknown value is an error. `-o` is a **deprecated** alias for `--format` on this command only (it prints a notice); on every other command `-o` is an output path. |
| `--vsa-outfile <path>` | (none) | Write the Verification Summary Attestation to a file. This, not `-o`, is where verify writes a file. |

 

Full verifier flag list is in [`cilock/internal/options/verify.go`](https://github.com/aflock-ai/rookery/blob/main/cilock/internal/options/verify.go).

 

Exit code **0** on policy pass, non-zero on any verification failure or error.

 

## `cilock verify-bundle`

 

> Verifies a **Sigstore bundle** (`application/vnd.dev.sigstore.bundle*`, the JSON format cosign and the Sigstore clients emit) under the Sigstore client verification procedure. It is a different format from the tar.gz of DSSE envelopes that [`cilock bundle`](https://www.testifysec.com/docs/cilock/reference/cli#cilock-bundle) builds and `cilock verify --bundle` reads. The flags are the sigstore-conformance CLI protocol's.

 

A certificate-signed bundle must match both `--certificate-identity` (the exact SAN) and `--certificate-oidc-issuer` (the exact issuer); neither may be empty. A bundle signed by a managed key is verified with `--key` instead, and passing `--key` together with either identity flag is an error.

 

Every check the trusted root can back is required. An SCT is required when the root distributes CT logs, and a transparency-log entry when it distributes Rekor logs. The signing time comes from a TSA timestamp or a log inclusion promise that the root can verify. Without `--trusted-root`, the public-good trusted root (or staging, with `--staging`) is fetched over TUF.

 

The one positional argument is the artifact: a file path, or its digest as `<sha256|sha384|sha512>:<hex>`.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--bundle <path>` | (required) | Path to the Sigstore bundle. |
| `--certificate-identity <san>` | (none) | Expected certificate SAN, exact match. |
| `--certificate-oidc-issuer <url>` | (none) | Expected certificate OIDC issuer, exact match. |
| `--key <path>` | (none) | PEM public key, for a bundle signed by a managed key. |
| `--trusted-root <path>` | public-good over TUF | Path to a `trusted_root.json`. |
| `--staging` | `false` | Use the Sigstore staging trusted root (over TUF). |

 

```bash
cilock verify-bundle --bundle app.sigstore.json \
  --certificate-identity https://github.com/org/repo/.github/workflows/release.yml@refs/heads/main \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  ./app
```

 

On success it prints `Verified OK` and exits **0**; any failed check exits non-zero with the reason.

 

## `cilock bundle`

 

> Build or inspect a portable attestation **bundle** (a tar.gz of DSSE envelopes) — the offline-evidence companion to [`cilock verify --bundle`](https://www.testifysec.com/docs/cilock/reference/cli#cilock-verify). `create` walks Archivista's subject graph from a digest and packs everything reachable; `inspect` prints a bundle's manifest so you can see what's inside before verifying.

 

### `cilock bundle create`

 

Pulls every DSSE envelope reachable from the given subject digest(s) via Archivista's subject graph and packs them into a tar.gz.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--subject, -s <digest>` | (required) | Subject digest(s) to seed the graph walk (e.g. `sha256:abc...`). Repeatable. |
| `--output, -o <path>` | stdout | Path to write the bundle (tar.gz). |
| `--max-depth <n>` | `5` | Maximum subject-graph traversal depth. |
| `--max-envelopes <n>` | `10000` | Maximum envelopes to fetch before aborting. |

 

```bash
cilock bundle create -s sha256:<digest> -o evidence.tar.gz
```

 

### `cilock bundle inspect`

 

Print a bundle's manifest and a per-envelope summary.

 

| Flag | Default | Description |
| --- | --- | --- |
| `<bundle.tar.gz>` | (required) | Bundle to inspect. |
| `--json` | `false` | Emit the manifest as JSON (suppresses the per-envelope summary). |

 

```bash
cilock bundle inspect evidence.tar.gz
# then verify offline against it:
cilock verify ./app -p policy.signed.json -k pub.pem --bundle evidence.tar.gz --platform-url ""
```

 

## `cilock fetch <gitoid>`

 

Not in CI/lock 4.4.0

`fetch` was added after the 4.4.0 release. On 4.4.0 and earlier, `cilock fetch` fails with `unknown command "fetch" for "cilock"`. It needs a `cilock` newer than 4.4.0.

 

> Download **any** attestation from Archivista by its gitoid. `fetch` is generic over attestation type — it downloads whatever DSSE envelope the gitoid names and knows nothing about the predicate inside it.

 

By default it writes the **exact stored bytes**, so re-hashing the saved file reproduces the gitoid it was fetched by. A decoded-then-re-marshalled envelope does not: JSON key order, whitespace and any member the struct has no field for are all lost, and the content address goes with them.

 

**What this verifies, and what it does not.** `fetch` verifies the **content address**: the bytes it writes are re-hashed locally and must equal the gitoid you asked for, so a compromised or on-path Archivista cannot hand you different evidence under the name you requested. It **does not verify the signature**, and it does not verify who signed. An envelope that downloads cleanly may be signed by anyone, or carry a signature that does not validate at all — [`cilock verify`](https://www.testifysec.com/docs/cilock/reference/cli#cilock-verify) is what establishes signer trust. `--payload` and `--predicate` output is content lifted out of an envelope whose signature `fetch` did not check.

 

The argument is the 64-character lowercase hex sha256 gitoid Archivista stores — the same string `cilock policy push` prints and the `gitoidSha256` field carries. A `gitoid:blob:sha256:<hex>` URI is refused with the hex part to pass instead; nothing is normalized, because silently rewriting a content address turns "you asked for the wrong object" into "you got an object you did not ask for".

 

| Flag | Default | Description |
| --- | --- | --- |
| `<gitoid>...` | (required) | One or more gitoids to download. More than one requires `--outdir`. |
| `--outfile, -o <path>` | stdout | Path to write the fetched attestation. A single gitoid only. |
| `--outdir <dir>` | (none) | Existing directory to write one file per gitoid into, named `<gitoid>.dsse.json`. Required for more than one gitoid. |
| `--payload` | `false` | Write the base64-decoded DSSE payload (the in-toto statement) instead of the envelope. |
| `--predicate` | `false` | Write only the statement's `predicate` member instead of the envelope. |
| `--force` | `false` | Overwrite the output if it already exists. |
| `--platform-url <url>` | logged-in platform | Platform whose Archivista to read, and whose session authorizes the read. |
| `--archivista-url <url>` | the platform's own Archivista | Third-party Archivista server. The session bearer is withheld from a different origin. |
| `--archivista-headers <h>` | (none) | Headers to send with each request (e.g. `Authorization: Bearer ...`). Repeatable. |

 

`--payload` and `--predicate` are mutually exclusive, as are `--outfile` and `--outdir`. Nothing is written to the destination unless the gitoid check passed: a mismatch, a 404 or an oversized body exits non-zero and leaves no partial or empty file behind, and `--force` stages the new bytes beside the destination so a failed fetch cannot destroy the file already there.

 

```bash
# Save an attestation's exact stored bytes; the saved file re-hashes to the gitoid
# (git's blob address over the same bytes — `git hash-object -t blob` with sha256)
cilock fetch <gitoid> -o evidence.json

# Several at once, one file per gitoid
cilock fetch --outdir ./evidence <gitoid> <gitoid>

# Just the predicate, for a tool that only wants the payload's contents
cilock fetch --predicate <gitoid> | jq .

# Then establish signer trust — fetch never did
cilock verify ./app -p policy.signed.json -k pub.pem
```

 

## `cilock attest`

 

> Records attestations against the current context **without wrapping a command** — sugar for `cilock run -- true`. Every `run` flag works here. Use it for consultative / at-rest attestors that snapshot state (e.g. `github-review`, `aws-iid`) rather than observe a command.

 

```bash
cilock attest -a github-review -k key.pem -o review.bundle.json -s review-head
```

 

### `cilock attest vex`

 

Authors an **OpenVEX** document from a triage decision, validates it against the spec's own rules, writes it to disk, and attests it with the same signer, timestamper, and Archivista destination every other command uses. Before this, cilock could only *consume* a VEX document, so producing one meant hand-written JSON that nothing checked.

 

Validation is fail-closed — a statement that suppresses a finding has to say why. Unknown statuses, unknown justifications, and malformed vulnerability IDs are rejected rather than recorded. Product digests are canonicalized to bare lowercase hex, which is how the platform stores an image digest at ingest, so a statement authored here joins to the image it is about.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--product <ref>` | (required) | Product the statement is about. Repeatable. An OCI reference with a digest (`ghcr.io/org/img@sha256:<hex>`), a package URL (`pkg:…`), a bare sha256 digest, or an AWS resource ARN (no wildcards). |
| `--vuln <id>` | (required) | Vulnerability the statement is about (`CVE-YYYY-NNNN`, `GHSA-xxxx-xxxx-xxxx`, or a scanner finding `<scanner>:<check>` such as `prowler:iam_root_mfa_enabled`, stored lowercase). Repeatable; each becomes its own statement carrying every `--product`. |
| `--status <status>` | (required) | `not_affected`, `affected`, `fixed`, or `under_investigation`. |
| `--justification <j>` | (none) | Why the product is not affected. **Required** with `--status not_affected`, rejected otherwise. One of `component_not_present`, `vulnerable_code_not_present`, `vulnerable_code_not_in_execute_path`, `vulnerable_code_cannot_be_controlled_by_adversary`, `inline_mitigations_already_exist`. |
| `--impact-statement <text>` | (none) | Free-text elaboration on the justification — where a human (or an agent) explains the reasoning a reviewer will read. |
| `--action-statement <text>` | (none) | What is being done about it. **Required** with `--status affected`: an affected statement with no remediation plan is a finding, not triage. |
| `--author <name>` | `cilock` | Author recorded in the OpenVEX metadata. The cryptographic identity is the signer; this is the human-readable attribution. |
| `--author-role <role>` | (none) | Optional role of the author (OpenVEX `role`). |
| `--vex-out <path>` | `vex.openvex.json` | Where the OpenVEX document lands. The signed bundle still goes to `--outfile`. |

 

Every [`cilock run`](https://www.testifysec.com/docs/cilock/reference/cli#cilock-run-cmd) flag applies — signer selection, `--outfile`, `--platform-url`, Archivista upload.

 

```bash
# Triage a CVE as unreachable in a specific image, signed keyless
cilock attest vex \
  --product ghcr.io/acme/api@sha256:2f3c50f223e2c4e30b100dbcbaa5ff5e2f4bffd09b57ff0a687e766e79a76d1f \
  --vuln CVE-2024-12345 \
  --status not_affected \
  --justification vulnerable_code_not_in_execute_path \
  --impact-statement "the vulnerable parser is never reached from the request path" \
  -s vex-triage -o vex.bundle.json

# Accept a finding with a remediation plan, signed with a local key
cilock attest vex \
  --product pkg:golang/github.com/example/mod@v1.2.3 \
  --vuln CVE-2024-99999 \
  --status affected \
  --action-statement "upgrade to v1.2.4 in the next release train" \
  -k cosign.key -s vex-triage -o vex.bundle.json
```

 

## `cilock policy draft`

 

> Turns a hand-authored policy source into a complete, verifiable policy document without you looking up a trust root. You write the part that expresses intent — the steps, the functionaries, the rego — and leave the trust material out; draft sends that source to the platform, which fills in the tenant's platform Fulcio roots and timestamp authorities, validates the result, and returns it. draft then verifies the returned digest against the bytes it actually received before writing to `--output`.
> 
>  
> 
> **draft never signs.** The hydrated document is inert until a human signs it, and that is deliberate: signing is what makes a policy authoritative, so it stays an explicit human act with a human identity. draft prints the exact `cilock sign` command rather than running it. Needs a logged-in session with the `policy:validate` scope — if the platform rejects the call for a missing scope, run `cilock login` again to pick it up.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--datatype, -t <uri>` | `https://aflock.ai/policy/v0.1` | Policy payload type sent for hydration. |
| `--file, -f <path>` | (required) | Path to the hand-authored policy source. |
| `--force` | `false` | Overwrite `--output` if it already exists. |
| `--output, -o <path>` | the source path with a `.hydrated.json` suffix | Where to write the hydrated, UNSIGNED policy. |
| `--platform-url <url>` | the logged-in platform | TestifySec platform URL. |

 

```bash
# Hydrate a hand-authored policy — writes policy.hydrated.json, UNSIGNED
cilock policy draft -f policy.json

# Then sign it yourself; cilock never signs a policy for you:
cilock sign -f policy.hydrated.json -o policy.hydrated.signed.json
```

 

## `cilock policy from-bundles`

 

> Reads one or more signed attestation bundles and emits a **starter Witness policy** — one step per bundle (step name = the bundle basename without the `.bundle.json` suffix), functionaries populated from each signing keyid, and `attestations[]` populated from the predicate types found. Edit, then sign with `cilock sign`. Use `--step-prefix` to prepend a prefix to every generated step name.

 

The TSA certificates embedded in the bundles are never trusted: evidence cannot vouch for its own signing time. So a keyless policy comes out with an empty `timestampauthorities[]`, and `cilock verify` rejects its short-lived leaves. `--trust-platform-tsa` anchors the TSA root your platform publishes instead. It fetches the chain from the platform's discovery document (`tsa_cert_chain_url`, https and same-origin only) and checks it against the trust pin `cilock verify` recorded. It writes the self-signed root to `timestampauthorities["platform-tsa"]` and prints each certificate's sha256 for review.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--expires <dur>` | `8760h` (1 year) | How far in the future the policy's `expires` field is set. |
| `--output, -o <path>` | `-` (stdout) | Write the generated policy here. |
| `--platform-url <url>` | the logged-in platform | Platform whose discovery document supplies the TSA chain. Only valid with `--trust-platform-tsa`. |
| `--publickey, -k <path>` | (none) | Public-key PEM file(s) for the bundles' signers. Repeatable. |
| `--step-prefix <str>` | (none) | Optional prefix prepended to every generated step name. |
| `--trust-platform-tsa` | `false` | Anchor the platform's published TSA root in `timestampauthorities["platform-tsa"]`. Evidence-embedded TSA certificates are never trusted. |

 

```bash
cilock policy from-bundles -k signer.pub build.bundle.json scan.bundle.json -o policy.json
# keyless bundles: anchor the platform TSA so the policy verifies
cilock policy from-bundles build.bundle.json -o policy.json --trust-platform-tsa
```

 

## `cilock policy from-commit <commit-sha>`

 

> Authors a starter Witness policy from the CI attestations the platform already holds for a commit — no local bundle files needed. It resolves the commit, finds every DSSE whose subjects include it, groups them by witness collection name (one step per collection), populates functionaries from each collection's signers (raw keyid or Fulcio keyless cert with the leaf SAN email pinned), leaves `timestampauthorities[]` empty unless `--trust-platform-tsa` anchors the platform's published TSA root (the evidence's own TSA certificates are never trusted), and wires cross-step provenance edges. Author-only by default (write the policy, then `cilock sign` → [`policy push`](https://www.testifysec.com/docs/cilock/reference/cli#cilock-policy-push---file---definition---tag) → [`policy bind`](https://www.testifysec.com/docs/cilock/reference/cli#cilock-policy-bind---definition---product)); pass both `--product` and `--tag` for the one-shot derive → sign → push → bind flow. The Archivista query needs a logged-in session; publication needs `policy:publish`, and the optional Product binding additionally needs legacy `policy:write`.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--definition, -d <name>` | the product name | PolicyDefinition name for the one-shot flow. |
| `--description <str>` | (none) | Description used only when the one-shot flow creates a new PolicyDefinition. |
| `--expires <dur>` | `8760h` (1 year) | How far in the future the policy's `expires` field is set. Set short and re-issue after review. |
| `--output, -o <path>` | `-` (stdout) | Write the authored policy here. Ignored in one-shot mode. |
| `--platform-url <url>` | the logged-in platform | TestifySec platform URL. |
| `--product, -p <id\|name>` | (none) | Product id or exact name. With `--tag`, runs the one-shot sign→push→bind flow against this product. |
| `--step-prefix <str>` | (none) | Optional prefix prepended to every generated step name (e.g. `release-`). |
| `--tag, -t <t>` | (none) | Release tag for the one-shot flow (requires `--product`). |
| `--trust-platform-tsa` | `false` | Anchor the session platform's published TSA root in `timestampauthorities["platform-tsa"]` (discovery `tsa_cert_chain_url`, https, same origin, checked against the trust pin). Evidence-embedded TSA certificates are never trusted. |

 

```bash
# Author a policy from a commit's CI evidence, write it for review
cilock policy from-commit 1a2b3c4d... -o policy.json

# One-shot: derive, sign keyless, publish a release tagged v1, bind to a product
cilock policy from-commit 1a2b3c4d... --product my-service --tag v1
```

 

## `cilock policy draft --file`

 

> Turns a hand-authored policy source into a complete, verifiable policy document without you looking up a trust root. You write the part that expresses intent — the steps, the functionaries, the rego — and leave the trust material out; `draft` sends that source to the platform, which fills in the tenant's platform Fulcio roots and timestamp authorities, validates the result, and returns it. `draft` then verifies the returned digest against the bytes it actually received and writes the hydrated document to `--output`. It does **not** sign: the hydrated document is inert until a human signs it, so `draft` never invokes [`cilock sign`](https://www.testifysec.com/docs/cilock/reference/cli#cilock-sign-file) for you — it prints the exact command to run. Needs a logged-in session with the `policy:validate` scope; if the platform rejects the call for a missing scope, run `cilock login` again to pick it up.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--file, -f <path>` | (required) | Path to the hand-authored policy source. |
| `--output, -o <path>` | the source path with a `.hydrated.json` suffix | Where to write the hydrated, UNSIGNED policy. |
| `--force` | `false` | Overwrite `--output` if it already exists. |
| `--datatype, -t <url>` | `https://aflock.ai/policy/v0.1` | Policy payload type sent for hydration. |
| `--platform-url <url>` | the logged-in platform | TestifySec platform URL. |

 

```bash
# Hydrate a hand-authored policy — writes policy.hydrated.json, UNSIGNED
cilock policy draft -f policy.json

# Then sign it yourself; cilock never signs a policy for you:
cilock sign -f policy.hydrated.json -o policy.hydrated.signed.json

# Explicit output, overwriting a previous draft
cilock policy draft -f policy.json -o hydrated.json --force
```

 

## `cilock policy push --file --definition --tag`

 

> Publishes an author-signed Witness policy to the platform. It uploads the signed policy DSSE to the platform's Archivista (the same upload path as `cilock run --enable-archivista`), ensures the named PolicyDefinition exists (creating it if absent), then creates a PolicyRelease that pins the definition to the uploaded policy under `--tag`. The policy file must already be DSSE-signed — produce it with `cilock sign` against the platform's keyless Fulcio. The DSSE upload needs `attestation:upload`; creating the immutable definition and release needs `policy:publish`. That scope cannot bind a Product or assign a Pushgate repository.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--file, -f <path>` | (required) | Path to the DSSE-signed policy (from `cilock sign`). |
| `--definition, -d <name>` | (required) | PolicyDefinition name; created if it doesn't exist. |
| `--tag, -t <t>` | (required) | Release tag (e.g. a semver or string). |
| `--description <str>` | (none) | Description used only when creating a new PolicyDefinition. |
| `--platform-url <url>` | the logged-in platform | TestifySec platform URL. |

 

```bash
# Sign first, then publish a release tagged v1.0.0
cilock sign -f policy.json -o policy.signed.json
cilock policy push --file policy.signed.json --definition supply-chain --tag v1.0.0
```

 

## `cilock policy publish --file --definition --tag`

 

> `draft` + sign + `push` as a single ceremony, for a policy a **human** signs. It hydrates the hand-authored source against the platform (the same call `cilock policy draft` makes), then starts a sign-in whose approval names that exact hydrated document. Your human opens the printed URL, reads what the policy requires, and approves with a passkey; the platform signs those exact bytes **as them** — never as you, and never at a lower assurance level than the session it observed — then creates the PolicyRelease.
> 
>  
> 
> You never hold a key, and your human never runs a second command. Before asking for the approval, cilock re-computes the digest of the hydrated bytes it holds, checks it against the source it sent and against the tenant that answered, and sends those same bytes at signing time — the platform refuses the pair if they disagree, so what is signed cannot drift from what was shown. Use `cilock policy push` instead when the policy is already DSSE-signed by an author key.
> 
>  
> 
> The release is created **Off**. Turning it on for a repository stays a separate act on Pushgate.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--file, -f <path>` | (required) | Path to the hand-authored (unsigned) policy source. |
| `--definition, -d <name>` | (required) | PolicyDefinition name; created if it does not exist. |
| `--tag, -t <t>` | (required) | Release tag, e.g. `v1`. |
| `--description <str>` | (none) | Description used only when the ceremony creates a new PolicyDefinition. |
| `--platform-url <url>` | the logged-in platform | TestifySec platform URL. |
| `--datatype <type>` | `https://aflock.ai/policy/v0.1` | Policy payload type. |

 

```bash
# Publish v1 of judge-gates from a hand-authored source
cilock policy publish -f deploy/pushgate/judge-gates.policy.json -d judge-gates -t v1
```

 

## `cilock policy bind --definition --product`

 

> Binds a published policy to a product on the platform. It resolves the named PolicyDefinition and the target product, then creates a PolicyBinding linking them. Pass `--release` (a release id) or `--tag` (resolved to a release under the definition) to pin a specific release. One of them is required: a binding with no release is enforced nowhere, so bind refuses to create one. Creating the binding needs `policy:write`.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--definition, -d <name>` | (required) | PolicyDefinition name. |
| `--product, -p <id\|name>` | (required) | Product id or exact name to bind to. |
| `--release <id>` | (none) | PolicyRelease id to bind (overrides `--tag`). |
| `--tag, -t <t>` | (none) | Release tag to resolve under the definition. |
| `--platform-url <url>` | the logged-in platform | TestifySec platform URL. |
| `--dry-run` | `false` | Resolve the definition, release and product and print the binding without creating it. |

 

```bash
# Bind a definition's v1.0.0 release to a product (by exact name)
cilock policy bind --definition supply-chain --tag v1.0.0 --product my-service
```

 

## `cilock policy bind pushgate`

 

> Prepares a repository assignment and opens its review on Pushgate's Gates page. The only act that changes anything is a human's: a tenant owner or admin picks the mode, gives the reason, presses Sign and apply, and completes the approval bound to that exact change. The command does not wait for the assignment and never reports it done.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--release <uuid>` | (required) | Exact PolicyRelease id to assign; a name, tag or latest never selects one. |
| `--repo <route>` | (required) | Repository route, e.g. `github.com/owner/name`. |
| `--mode <warn\|block>` | `warn` | Mode the human should choose in the review. |
| `--reason <text>` | (none) | Reason to show in the review (the human types the final one). |
| `--json` | `false` | Emit one JSON object (`repository`, `release`, `mode`, `reason`, `review_url`, `opened`, `applied`). `applied` is always `false`. |
| `--platform-url <url>` | the selected login | Platform whose discovery names the Pushgate origin. |

 

```bash
cilock policy bind pushgate --release 11111111-1111-4111-8111-111111111111 \
  --repo github.com/acme/widget --mode warn --reason "one-day side by side"
```

 

## `cilock keyid show`

 

> Prints the canonical keyid — `hex(sha256(PEM(pubkey)))` — derived from a public or private key. The same value that appears in policy `functionaries[].publickeyid` and in attestation signatures. Reads PEM public keys (PKIX) or private keys (PKCS#8/PKCS#1/SEC1; the public half is extracted). One line per input (`<keyid>  <path>`, matching `sha256sum`'s shape). Keys come from positional args or `-k/--key`.

 

| Flag | Default | Description |
| --- | --- | --- |
| `<key-file>...` / `--key, -k <path>` | (required) | Public or private key(s). `-k` takes a single file; mixing `-k` with positional args is an error. |
| `--format <fmt>` | `text` | `text` = sha256sum-style lines; `json` = JSON array (for `jq`). |

 

```bash
cilock keyid show signer.pub
cilock keyid show signer.key signer.pub other.pem
cilock keyid show --format=json signer.key | jq .
```

 

## `cilock attestors list`

 

Prints a box-drawn table of every attestor compiled into the binary:

 

```text
┌──────────────────────────┬─────────────────────────────────────────────────────┬─────────────┐
│           NAME           │                        TYPE                         │  RUN TYPE   │
├──────────────────────────┼─────────────────────────────────────────────────────┼─────────────┤
│ git (default)            │ https://aflock.ai/attestations/git/v0.1             │ prematerial │
│ environment (default)    │ https://aflock.ai/attestations/environment/v0.1     │ prematerial │
│ material (always run)    │ https://aflock.ai/attestations/material/v0.3        │ material    │
│ command-run (always run) │ https://aflock.ai/attestations/command-run/v0.2     │ execute     │
│ product (always run)     │ https://aflock.ai/attestations/product/v0.3         │ product     │
│ inclusion-proof          │ https://aflock.ai/attestations/inclusion-proof/v0.1 │ postproduct │
│ material-v0.1            │ https://aflock.ai/attestations/material/v0.1        │ material    │
│ product-v0.1             │ https://aflock.ai/attestations/product/v0.1         │ product     │
│ product-v0.2             │ https://aflock.ai/attestations/product/v0.2         │ product     │
│ ...                      │ ...                                                 │ ...         │
└──────────────────────────┴─────────────────────────────────────────────────────┴─────────────┘
```

 

Run types are lowercase strings: `prematerial`, `material`, `execute`, `product`, `postproduct`, `verify`.

 

Markers: `(always run)` means the attestor runs on every `cilock run`; `(default)` means it's enabled by default and you don't need to pass it via `--attestations`. The full catalog is in the [attestor catalog](https://www.testifysec.com/docs/cilock/reference/attestor-catalog).

 

## `cilock attestors schema <name>`

 

Prints the JSON Schema document for the named attestor's predicate. Useful for writing Rego policies against a specific schema.

 

## `cilock policy validate -p <path>`

 

Validates a Witness/cilock policy document for schema correctness. An unsigned policy is the normal input (validate, then `cilock sign`) and produces no signature warning; a DSSE envelope that carries no signatures does.

 

A raw draft from `cilock policy template` carries two empty platform trust placeholders, `roots.fulcio-root` and `timestampauthorities.platform-tsa`, each exactly `{"certificate": ""}`, which the platform fills when a human signs. A draft whose only gap is those placeholders passes as `PASSED (unsigned draft)` (exit 0), lists them (`placeholders` in JSON), and says it is not releasable. Any other empty root, a placeholder in any other shape, and a placeholder inside a DSSE envelope stay errors. `--strict` refuses the placeholders too.

 

| Flag | Short | Default | Description |
| --- | --- | --- | --- |
| `--policy <path>` | `-p` | (required) | Policy to validate: raw JSON or a signed DSSE envelope. |
| `--publickey <path>` | `-k` | (none) | Verify the envelope's signature against this key. Requires a DSSE envelope. |
| `--require-signed` | (none) | `false` | Fail unless the policy is a DSSE envelope with at least one signature (presence only; add `-k` to verify it). |
| `--strict` | (none) | `false` | Also fail on the empty platform trust placeholders an unsigned draft carries. |
| `--format <fmt>` | (none) | `text` | `text` or `json`. `--output`/`-o` are deprecated aliases that print a notice. |

 

## `cilock policy guide`

 

Explains how to evidence each Pushgate goal with cilock, for the model that writes the policy. For each goal it prints the attestation types, what the wrapped command must leave behind, the exact `cilock run` line, the predicate fields a rule reads, the seeded fail-closed Rego, and the tools the detection catalog knows for it (suggestions, never decisions).

 

| Flag | Short | Default | Description |
| --- | --- | --- | --- |
| `--goal <id>` | (none) | (all, summarized) | Goal to explain in full (repeat): `tests`, `quality`, `app-build`, `secrets`, `provenance`, `sbom`, `config`, `config-security`, `kubernetes`, `dockerfile`, `docs`, `migrations`, `image-vulns`, `vulns`. |
| `--topic <name>` | (none) | (none) | Topic to explain (repeat): `attestors`, `chain`, `flow`, `rego`, `rules`, `trace`, `vex`. |
| `--dir <path>` | `-d` | `.` | Repository to look at for tool suggestions. |
| `--format <fmt>` | (none) | `text` | `text` or `json`. |

 

```bash
cilock policy guide --goal tests --goal quality
cilock policy guide --topic chain --topic trace
```

 

## `cilock skill`

 

> The Pushgate agent skill ships inside cilock, so the copy you install matches the binary's commands.

 

### `cilock skill install`

 

Writes the Pushgate skill where the chosen agent discovers skills (`claude`: `~/.claude/skills/pushgate` or `.claude/skills/pushgate`; `codex`: `~/.agents/skills/pushgate` or `.agents/skills/pushgate`; `opencode` reads both). Re-running is safe. cilock records a SHA-256 digest of each file it writes and replaces only files that still match it; it refuses a file it did not write, or one edited since, unless `--force`. It writes only inside the skill directory and never through a symbolic link.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--agent <name>` | `auto` | `claude`, `codex`, `opencode`, or `auto` (detect from the environment). |
| `--scope <scope>` | `user` | `user` (every project on this machine) or `project` (this repository only). |
| `--dir <path>` | (none) | Install into `<dir>/pushgate` instead of the agent's skills directory. |
| `--force` | `false` | Replace files cilock did not write, or that were edited since. |

 

```bash
cilock skill install
cilock skill install --agent codex --scope project
```

 

### `cilock skill path`

 

Prints the directory `cilock skill install` would write for the same flags on its first line, then whether the skill is installed there and the discovery rule that makes the agent find it. Writes nothing. Takes the same `--agent`, `--scope` and `--dir` flags as `install`.

 

### `cilock skill show [file]`

 

Prints the skill embedded in this cilock to stdout: `SKILL.md` by default, or a named file such as `references/refusals.md`.

 

## `cilock policy template`

 

Writes a Pushgate policy draft skeleton: the enrolled agent functionary, the empty platform trust placeholders, an expiry one year out, one step per goal with its attestation types and seeded fail-closed Rego. Every place only you can judge for this repository is a `__FILL__` string. It never overwrites a file without `--force`, never touches a step it did not add, and never replaces a rule that is not an unfilled slot.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--goal <id>` | (none) | Goal to create a step for (repeat). |
| `--policy <path>` / `-p` | (none) | Existing draft to add a step to or fill. |
| `--add-step <name>` | (none) | Append a step with this name to the draft named by `-p`. |
| `--attestor <name>` | (none) | With `--add-step`: an attestor name or predicate type the step requires (repeat). |
| `--artifacts-from <step>` | (none) | With `--add-step`: a step whose products this step's materials must match (repeat). |
| `--attestations-from <step>` | (none) | With `--add-step`: a step whose predicates this step's rules read as `input.steps.<step>` (repeat). |
| `--fill <step>.<rule>=<json>` | (none) | Fill a rule slot (repeat). |
| `--force` | `false` | Overwrite an existing file when creating a draft. |

 

```bash
cilock policy template --goal tests --goal secrets --goal quality
cilock policy template -p .pushgate/policy.json --add-step docs-build --attestor command-run
```

 

## `cilock policy prove`

 

Proves a filled draft locally and offline, and never rewrites your steps or rules. It refuses a draft with a `__FILL__` slot, records every step for real under a throwaway key, records each command-run step again wrapping `false`, and verifies that the real evidence passes and each failing run is refused by its own step. It then validates the draft as `cilock policy validate` does and deletes the key and evidence. The first line is exactly `Local verify: passed` or `Local verify: REFUSED by <step>: <rule message>`. It exits non-zero on any refusal and still writes the draft, so your human can choose. It never signs the real policy, uploads, or activates.

 

| Flag | Short | Default | Description |
| --- | --- | --- | --- |
| `--policy <path>` | `-p` | `.pushgate/policy.json` | The draft to prove. |
| `--run <step>=<argv>` | (none) | (the step's command pin) | The command for a step, as a JSON array or plain words (repeat). |
| `--step <name> -- <argv>` | (none) | (none) | One step's command after `--`. |
| `--run-arg <flag>` | (none) | (none) | Pass an attestor flag through to every recorded run (repeat). |
| `--output <path>` | `-o` | (back to `-p`) | Write the normalized draft here. |
| `--workingdir <dir>` | `-d` | (current directory) | Directory each step's command runs in. |
| `--trace` | (none) | `false` | Record every step with `--trace`. |
| `--platform-url <url>` | (none) | (the cilock default platform) | Platform whose enrolled agent the functionary names. |
| `--no-normalize` | (none) | `false` | Prove and validate the draft as written, without filling the functionary or trust placeholders. |

 

```bash
cilock policy prove -p .pushgate/policy.json
cilock policy prove -p .pushgate/policy.json --step app-build -- go build -o bin/app ./cmd/app
```

 

## `cilock policy input <envelope.json>`

 

Decodes a signed step envelope (the `-o`/`--outfile` of `cilock run` or `cilock attest`) and shows what a Rego rule for that step reads as `input`. Without `--attestor` it lists the step, its subject count, and each attestation with its top-level fields. With `--attestor` it prints exactly that attestation's JSON, the object a rule on that attestor evaluates. A rule reads it as `input.attestation` when the verifier adds cross-step (`attestationsFrom`) or timestamp context, which a platform-signed step always has, and as bare `input` otherwise; read it as the seeded rules do, `pred := object.get(input, "attestation", input)`. An attestor the envelope does not carry is an error that names the ones it does.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--attestor <name>` | (none) | Print one attestation's input: a short name such as `command-run`, or its full type URI. |

 

```bash
cilock policy input tests.json
cilock policy input tests.json --attestor command-run
```

 

## `cilock tools`

 

> The catalog of detectors cilock knows how to auto-fire (the same source [cilock.dev's tool pages](https://www.testifysec.com/docs/cilock/tools) render from). `list` enumerates them, `show` prints one tool's full record, `test-plan` emits a validation plan.

 

### `cilock tools list`

 

List every detector cilock knows how to auto-fire.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--category <cat>` | (all) | Filter by lexicon category (e.g. `build`, `vulnerability-scan`, `ci-context`, `sbom-generate`). |
| `--source <src>` | (all) | Filter: `attestor-backed` \| `catalog-only`. |
| `--format <fmt>` | `table` | `table` or `json`. |

 

```bash
cilock tools list
cilock tools list --category vulnerability-scan --format json
```

 

### `cilock tools show <name>`

 

Show full catalog detail for one tool/attestor — the same record the website generates from.

 

| Flag | Default | Description |
| --- | --- | --- |
| `<name>` | (required) | Tool/attestor to show (e.g. `sarif`). |
| `--section <slug>` | (all) | Print only one documentation section, by slug (see the summary). |
| `--format <fmt>` | `text` | `text` or `json` (the full machine-readable record). |

 

```bash
cilock tools show sarif
cilock tools show sarif --section policy-gotcha
cilock tools show sarif --format json
```

 

### `cilock tools standards`

 

Print the SLSA Build and ALPS guidance catalog: each level's requirement, how cilock observes it, and the ordered next steps that raise a run's ceiling. `cilock run` and `cilock verify` read this catalog to print an observed **ceiling** per standard, never a verified level (see [Standards guidance](https://www.testifysec.com/docs/cilock/reference/standards-guidance)). Steps marked `planned` are coming and carry no copyable snippet in run or verify output; levels marked `future` get no action.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--format <fmt>` | `text` | `text` or `json` (the catalog the docs page generates from). |

 

```bash
cilock tools standards
cilock tools standards --format json
```

 

### `cilock tools test-plan`

 

Emit a structured test plan describing how to validate each detector (what triggers it, the expected fire decision, and a negative case). Pipe `--format=json` into a runner that exercises each scenario against `cilock plan`.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--only <name>` | (all) | Limit the plan to a single detector. |
| `--format <fmt>` | `markdown` | `markdown` or `json`. |

 

```bash
cilock tools test-plan
cilock tools test-plan --only sarif --format json
```

 

## `cilock skill`

 

> The Pushgate skill teaches a coding agent the cilock and Pushgate loop: enroll the agent, produce signed evidence, act on a refusal, and draft a policy that a human signs. It ships inside the binary, so it matches that binary's commands. Flags, predicate fields and rule templates stay in cilock's own help and output, where they cannot drift. Claude Code users can install the same skill as a plugin (`/plugin marketplace add aflock-ai/rookery`, then `/plugin install pushgate@rookery`); use one or the other, or the skill loads twice.

 

### `cilock skill install`

 

Write the skill where the chosen agent discovers skills:

 

| Agent | `--scope user` | `--scope project` |
| --- | --- | --- |
| `claude` | `~/.claude/skills/pushgate` | `.claude/skills/pushgate` |
| `codex` | `~/.agents/skills/pushgate` | `.agents/skills/pushgate` |
| `opencode` | `~/.config/opencode/skills/pushgate` | `.opencode/skills/pushgate` |

 

Project scope installs at the root of the Git repository you are in. opencode also reads the claude and codex locations, so one install covers it.

 

Re-running is safe. cilock records a SHA-256 digest of each file it writes (`.cilock-skill.json`) and replaces only files that still match that digest. It refuses to overwrite a file it did not write, or one edited since, unless you pass `--force`. It writes only inside the skill directory and never through a symbolic link.

 

| Flag | Default | Description |
| --- | --- | --- |
| `--agent <name>` | `auto` | `claude`, `codex`, `opencode`, or `auto` (detect from the environment). |
| `--scope <scope>` | `user` | `user` (every project on this machine) or `project` (this repository only). |
| `--dir <path>` | (none) | Install into `<dir>/pushgate` instead of the agent's skills directory. |
| `--force` | `false` | Replace files cilock did not write, or that were edited since. |

 

```bash
cilock skill install                              # detect the agent, install for this user
cilock skill install --agent codex --scope project
cilock skill install --dir ./vendor-skills
```

 

### `cilock skill path`

 

Print the directory `cilock skill install` would write for the same flags, on the first line by itself, followed by whether the skill is installed there and the discovery rule that makes the agent find it. Writes nothing. Takes `--agent`, `--scope` and `--dir` with the same meaning as `install`.

 

```bash
cilock skill path --agent claude --scope project
```

 

### `cilock skill show`

 

Print the skill embedded in this cilock to stdout. With no argument it prints `SKILL.md`; name a file, such as `references/refusals.md`, to print that one.

 

```bash
cilock skill show
cilock skill show references/refusals.md
```

 

## `cilock get <tool>`

 

Install a supporting tool from a **SHA-256 pin embedded in this `cilock` binary**. The pinned URL, release-artifact digest, and (for archives) the extracted-executable digest all live in one reviewable manifest compiled into the binary, so "which build of syft did CI run?" is answered by the cilock version alone — not by whatever a package manager or `curl | sh` happened to resolve that day.

 

The command **fails closed**: an unknown tool, a missing or malformed pin, or a digest mismatch aborts before anything is installed. Archives (`.tar.gz`, `.zip`) are verified *before* extraction and the extracted executable is hashed again against its own pin.

 

| Flag | Default | Description |
| --- | --- | --- |
| `<tool>` | (required) | Tool to install. Passing an unknown name fails and prints the available tools (today: `syft`, `zarf`). |
| `--dest <dir>` | `$HOME/.cilock/bin` | Installation directory. |
| `--verify` | `false` | Hash the already-installed executable against its embedded pin and exit — no download, no write. |

 

```bash
# Install the pinned Syft release to $HOME/.cilock/bin
cilock get syft

# Install Zarf into a repo-local bin directory
cilock get zarf --dest ./bin

# Confirm an existing installation still matches its pin
cilock get syft --verify
```

 

The install path is printed on success, along with the `export PATH=…` line needed to use it (on Windows, the directory to add to `PATH`). An existing installation is replaced atomically; on Windows the previous binary is backed up and **restored** if the swap fails, so a failed upgrade never destroys a working tool.

 

Upgrading a pin is a deliberate, reviewable change to the manifest in the `cilock` source tree — there is no `--version` flag, because a version selected at runtime would defeat the point of pinning.

 

## `cilock completion <shell>`

 

Standard cobra completion. Supported shells: `bash`, `zsh`, `fish`, `powershell`.

 

## `cilock version`

 

Prints `cilock <version>`. The version string is injected at build time via `-ldflags="-X 'github.com/aflock-ai/rookery/cilock/cli.Version=<version>'"`.

 

## `cilock license`

 

Prints the license under which this binary is distributed, plus any branded-distribution metadata baked in at build time. The **stock `cilock` CLI** is **Apache 2.0**, so a stock binary shows the Apache 2.0 statement. Binaries produced by the [`rookery-builder`](https://www.testifysec.com/docs/cilock/guides/build-a-custom-cilock) link the same CLI and likewise print the Apache 2.0 statement (the builder's own distribution terms are covered under [licensing](https://www.testifysec.com/docs/cilock/ecosystem/rookery#licensing)). Custom binaries built with `--customer X --tenant Y` additionally show:

 

```text
Built for: X
Tenant:    Y
```

 

The CustomerID and TenantID are injected via `-ldflags="-X 'github.com/aflock-ai/rookery/cilock/cli.CustomerID=...' -X 'github.com/aflock-ai/rookery/cilock/cli.TenantID=...'"`.

 

## Configuration

 

CI/lock is **args-only**: there is no config file. CLI flags (highest precedence), a small set of `CILOCK_*` env vars, and built-in defaults are the entire configuration surface. See [Configuration](https://www.testifysec.com/docs/cilock/reference/configuration) for the override hierarchy. (The legacy `.witness.yaml` config file inherited from the witness lineage was removed deliberately — a config file in a cloned repo could silently override security-critical flags.)

Reference generated from the product documentation. Match commands and support details to your installed release.
