cilock CLI reference
Source of truth:
rookery/cilock/cmd/cilock/main.goandrookery/cilock/cli/. Defaults and flag names below trackcilockas built frommain, 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; checkcilock versionbefore you rely on it.
cilock - Collect and verify attestations about your build environmentsCI/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 pushgate connect | Redeem a Pushgate setup link and push through the gate with the credential kept out of every URL, config value and output. |
cilock pushgate disconnect | Remove the stored Pushgate push credential and the push URL connect set in this clone. |
cilock pushgate credential | Git credential helper for the Pushgate push credential; git runs it, for exactly one repository URL. |
cilock node enroll / status | Register this host's TPM with the platform as an ALPS 2 node, and show what an approver compares. |
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. Needs a cilock built with -tags sigstore_verify. |
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 kyverno explain <image@digest> | Say in plain words why Kyverno would admit or deny an image that carries VSAs. Not in a release yet. |
cilock kyverno policy --keyless | Render a Kyverno ImageValidatingPolicy that admits on the platform's keyless VSA, with the platform's trust inline. Not in a release yet. |
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 attach <repo>@sha256:<hex> | Attach signed attestations to an OCI image: Sigstore bundle referrers by default, or the cosign .att tag when the registry has no referrers API. |
cilock download <repo>@sha256:<hex> | Download the attestations cilock attach wrote to an OCI image, as envelopes with their timestamps. |
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). |
cilock --version | Print cilock <version>, the first line of cilock version, and exit. No shorthand: -v is plan --verbose. |
Global flags
These persistent flags are accepted on every subcommand:
| Flag | Default | Notes |
|---|---|---|
--log-level, -l <level> | info | One of debug, info, warn, error. On run and attest the default drops to warn under --summary short and error under --summary none; an explicit --log-level wins. |
--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. |
--no-color | false, or true when NO_COLOR is set and not empty | Write log output without colour escape sequences (no-color.org). An explicit --no-color=false turns colour back on. |
Every command that takes --format also takes --json, the same as --format json. --json together with any other --format value is a usage error (exit 64).
Exit codes
cilock help exit-codes prints this table.
| Code | Meaning |
|---|---|
0 | Success. |
1 | Failure or refusal: a command that ran and failed, or a policy that denied. |
2 | Verification could not be carried out (cilock verify), as distinct from 1, a policy that denied. |
64 | Usage error: an unknown command or flag, a malformed flag value, a wrong number of arguments, a missing required flag, or flags that may not be combined. A command's own check of its inputs, once parsed, fails with 1. |
Environment variables
cilock help environment lists every environment variable cilock defines or honours by name, with what each does: its own CILOCK_, CILOCKD_ and JUDGE_ variables, the legacy WITNESS_ and AFLOCK_ names it still reads, and conventions such as NO_COLOR and DO_NOT_TRACK. Variables a CI provider sets (GITHUB_*, GITLAB_CI, CI_*, ...) are read to detect where cilock runs, and attestors record them.
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. 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.
# 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. |
# 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-webcilock 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. |
cilock whoami
cilock whoami --platform-url https://platform.example.com
cilock whoami --jsoncilock 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. |
cilock logout
cilock logout --platform-url https://platform.example.comcilock enroll agent
Enroll an agent principal for this machine in one ceremony. A browser window opens on the platform's enrollment page; a person 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. |
cilock enroll agent
cilock enroll agent --name claude-on-dev-laptop --repo acme/widget --ttl 4hcilock 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). |
# Read the credential from stdin (preferred)
cilock agent login --platform-url https://platform.example.com \
--tenant-id <uuid> --agent-id <uuid> < credential.txtcilock 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. |
cilock agent logout --platform-url https://platform.example.comcilock 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. |
cilock agent status --platform-url https://platform.example.comcilock 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.
cilock agent migratecilock 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. |
cilock agent list --platform-url https://platform.example.comcilock 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. |
cilock agent remove 22222222-2222-2222-2222-222222222222cilock agent bearer
Exchange this machine's enrolled agent credential and write the platform bearer the exchange returns, and nothing else, to stdout, for a program reading the pipe (jade uses it to read evidence as the agent). The bearer uploads evidence and reads the evidence of exactly the tenant the agent was enrolled in; it expires with the exchange, two hours at most. The command refuses to write to a terminal, so the bearer does not land on a screen. A missing, pending, expired or refused agent exits non-zero with an error naming the agent and its tenant, and writes nothing to stdout. The refresh credential is never written.
| Flag | Default | Description |
|---|---|---|
--platform-url <url> | https://platform.testifysec.com | Platform the agent is enrolled with. |
cilock agent bearer --platform-url https://platform.example.com | program-that-reads-itcilock trust
Register an OIDC federated identity the platform will trust for keyless attestation upload — the CI complement to cilock run. 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 | Create the credential without asking. Required when stdin is not a terminal (CI, a script, a coding agent): without it cilock trust refuses there and exits 1, naming the flag. --dry-run needs no confirmation. |
# Trust a GitHub repo's Actions to upload (most common)
cilock trust github acme/widget
# From a script or CI job, where there is no terminal to confirm on
cilock trust github acme/widget --yes
# 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:prodcilock 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). |
# Check the default hosted platform
cilock doctor
# Check a self-hosted / standalone platform, machine-readable
cilock doctor --platform-url https://judge.example.com --jsoncilock 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. |
# 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 --globalcilock 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. When git maps the branch to a different remote
ref on that remote (its push destination or its upstream, at HEAD), status
asks about that ref and says which mapping it used. Nothing else is inferred:
after an ad-hoc push such as git push pushgate HEAD:refs/heads/other, a
not_found names the ref it looked up; pass --ref refs/heads/other to ask
about the pushed ref. 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. |
# 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 --jsoncilock pushgate connect
Redeems a Pushgate setup link and configures the current clone to push through
the gate. The push credential goes to CI/lock's owner-only store
(pushgate-credentials.json in the state directory). It never appears in a
remote URL, a Git config value, a process argument or the terminal.
connect sets one push URL and writes no credential.* configuration:
pushgate::https://pushgate.dev/gh/<owner>/<repo>.gitWith delivery enabled it replaces every push URL of origin (the fetch URL
stays on GitHub); otherwise it adds a pushgate remote, or replaces the push
URLs of the one you have. git remote -v shows the URL above.
The pushgate:: prefix makes git run git-remote-pushgate, which every CI/lock
installer links next to cilock. On each push it:
- refuses, with nothing sent, unless the store holds a credential for exactly
the URL git resolved, after any
insteadOfrewrite; - appends an empty
credential.helper, thencilock pushgate credential, thencredential.useHttpPath=trueto the push's command-scope configuration, which git reads after every config file, include, worktree config,GIT_CONFIG_COUNTentry and-c. That push asks CI/lock's helper and no other, with the repository path; - hands over to git's own HTTPS transport.
What connect does, in order:
- Discovers the Pushgate origin from the selected platform and refuses a link on any other origin before sending anything.
- Checks what it can without the credential: that this is a Git checkout,
that
git-remote-pushgateis onPATH, and that the store is writable and owner-only. - Redeems the single-use link (
POST /gh/<owner>/<repo>/agent-setup/redeem). If an agent is enrolled for this platform with a listed repository scope, it refuses a repository outside that scope. - Stores the credential, keyed by the exact repository URL. Connecting again replaces it.
- Sets the push URL. If that fails, the credential stays stored and connect
prints the one
gitcommand to run. - Reports whether
git push <remote>from this worktree goes only to the gate URL. connect replaces only the push URLs in the clone's own config, so one from a global, included or worktree config is reported, by remote name. It also names each remote URL key (such asremote.legacy.url) whose URL on the gate still carries a credential in its userinfo. It prints no URL that git returned, so a credential in one never reaches the terminal. It reports routing; it does not promise it. Only the gate URL releases the credential, and refusing direct pushes to GitHub is a repository rule.
connect does not rewrite a credential left in an old remote URL; it keeps working until it is revoked.
git-remote-pushgate reads the store from the environment of each push, so a
push run with a different CILOCK_STATE_DIR than connect is refused rather
than sent. connect does not run on Windows yet.
| Flag | Default | Description |
|---|---|---|
--platform-url <url> | selected login | The platform whose discovery names the trusted Pushgate origin. |
cilock pushgate connect 'https://pushgate.dev/gh/acme/api/agent-setup/prompt.md?agent=...&setup=...'
# With delivery enabled, origin pushes through the gate:
git push origin HEAD
# Without delivery, push to the remote connect added:
git push pushgate HEADcilock pushgate disconnect
Removes what connect set up. It removes the pushgate:: push URLs connect
wrote to this clone (the matching remote.origin.pushurl,
remote.pushgate.pushurl or remote.pushgate.url value, each by its exact
value, and the pushgate remote's fetch refspec when it is there and that
remote held nothing else), then deletes the push credential from CI/lock's
store, under the same lock connect's save takes. Only entries it observed are
removed: a value another writer adds meanwhile, branch settings, tracking refs
and other remotes are never touched. If a config change fails, the credential
is kept.
With no argument it reads the gate URL from this clone's push URLs. Pass the gate repository URL to remove a stored credential whose clone is gone; that form also runs outside a checkout.
- The store is shared by every clone on the machine, so every clone of the repository stops pushing through the gate.
- The credential stays valid in Pushgate until it is revoked there.
- Nothing stored or connected exits 0 and says so.
- A store it cannot read (not owner-only, not JSON, not a regular file) is an error, and no config is changed.
cilock pushgate disconnect
cilock pushgate disconnect https://pushgate.dev/gh/acme/api.gitcilock pushgate credential
Git's credential helper for the Pushgate push credential. It keeps the
credential out of the remote URL, Git config and process arguments: the
credential lives in CI/lock's state directory (pushgate-credentials.json,
owner-only), keyed by the exact repository URL it was issued for. You do not
run it by hand, and connect writes it into no config file:
git-remote-pushgate names it, with credential.useHttpPath, for each push,
and git runs it.
| Operation | What it does |
|---|---|
get | Prints the credential only when the protocol, host, port and path git sends all equal a stored repository. Anything else, including a request with no path, prints nothing, so git reports an ordinary authentication failure. |
store, erase | Read git's request and change nothing. Only redeeming a setup link writes the store; git sends erase after every failed authentication, and that must not delete a working credential. |
A store that anyone but its owner can read is refused, for reading and for
writing; restore owner-only permissions (0600).
# What git runs; shown for reference
printf 'protocol=https\nhost=pushgate.dev\npath=gh/acme/api.git\n\n' \
| cilock pushgate credential getcilock node enroll
Register this host's TPM with the platform as an ALPS 2 node. A human must
first approve the host's endorsement key (EK) in a tenant; pass that tenant's
ID. The command creates an attestation key and a node signing key in the TPM
once, saves their blobs in --state-dir, proves to the platform that both
live in the same TPM as the approved EK, and saves the registration. A retry
reuses the saved keys. The state directory must be owner-only (0700, file
0600): anyone who can read the blobs and open the TPM can sign as the node.
| Flag | Default | Description |
|---|---|---|
--tenant <id> | (required) | Tenant the EK was approved in. |
--platform-url <url> | https://platform.testifysec.com | Platform to register with. |
--tpm-device <path> | /dev/tpmrm0 | TPM device (the kernel resource manager). |
--state-dir <dir> | /var/lib/cilock-node | Private directory for the key blobs and registration. |
sudo -u cilock-node cilock node enroll --tenant <tenant-id>cilock node status
Print the SHA-256 of the TPM's EK public key (DER SubjectPublicKeyInfo), the
AWS command that prints the same value for a NitroTPM instance, and, once
enrolled, the node key fingerprint, the registration and the tenant. An
approver compares the EK hash before approving. A state directory that group
or other can read is an error, never shown as "not enrolled". Takes
--tpm-device and --state-dir as above.
cilock node statuscilock 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. Naming them in --attestations is accepted: cilock logs a warning, skips the duplicate and records each once.
By default a run signs with one signer (enforced in cilock/cli/signer_error.go). An explicit --signer-* key replaces the platform keyless identity that a login or CI job would otherwise supply. Pass --countersign to sign the same envelope with every configured signer instead; see Countersign an attestation.
Common flags
| Flag | Short | Default | Description |
|---|---|---|---|
--step <name> | -s | inferred | Step category. Optional — when omitted, inferred from the wrapped command. 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 (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. |
--summary <mode> | (none) | short in CI, else full | How much run summary to print on stderr: full, short (one line) or none (nothing on success; a failure still prints the short line). Also settable via CILOCK_SUMMARY (the flag wins). The wrapped command's output, the signed evidence and the --json result are never affected. See Run summary. |
--explain | (none) | false | Print the standards guidance in the run summary: why each SLSA Build and ALPS ceiling is what it is, and the steps that would raise it. Without it the full summary has one standards line. --json always carries the whole standards object. |
--trace | -r | false | Enable syscall tracing (Linux). Backend is ptrace+seccomp or eBPF — see 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. |
--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). Must be https://, except for local and private hosts. |
--timestamp-allow-http | (none) | false | Allow plain http:// TSA URLs on any host, for TSAs that serve RFC 3161 over HTTP only (DigiCert: http://timestamp.digicert.com). The token is still verified against the request; the digest being timestamped is visible on the network. Also on cilock sign; cilock verify has --vsa-timestamp-allow-http for --vsa-timestamp-servers. |
--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. |
--countersign | (none) | false | Sign with every configured signer. With a login or CI identity the platform keyless certificate signs and each --signer-* key (for example -k) adds a countersignature. Each signature gets its own timestamps. Fails before the command runs if fewer than two signers resolve, or if no identity is available for the keyless signature. |
--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. |
--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. |
--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 these signer providers:
file:--signer-file-key-path,--signer-file-cert-path,--signer-file-intermediate-paths,--signer-file-key-passphrase-pathfulcio:--signer-fulcio-url,--signer-fulcio-oidc-issuer,--signer-fulcio-oidc-client-id,--signer-fulcio-oidc-redirect-url,--signer-fulcio-token-path,--signer-fulcio-token-env(GitLab id_tokens variable, defaultSIGSTORE_ID_TOKEN),--signer-fulcio-use-http(defaulttrue)piv: the--signer-piv-*flags for a YubiKey PIV slot-9c key
Pass secrets by file, never as a flag value: any local user can read a command line through ps, and it lands in shell history and CI logs. A path flag takes /dev/fd/N too, so --signer-fulcio-token-path <(printf %s "$TOKEN") keeps the token off disk. --signer-file-key-passphrase, --signer-fulcio-token and --attestor-github-review-token still accept a literal, but are deprecated: each prints a warning to stderr naming its replacement (the GitHub token comes from GH_TOKEN or GITHUB_TOKEN). The warning never repeats the value. cilock login --token reads the token from stdin when given -; any other value warns the same way.
A rookery-builder binary that links a KMS backend (presets/all, or a custom build importing plugins/signers/kms/{aws,gcp,azure} or plugins/signers/vault-transit) also registers:
kms:--signer-kms-ref(key reference URI; the scheme must match a linked backend:awskms://,gcpkms://,azurekms://,hashivault://),--signer-kms-hashType(defaultsha256),--signer-kms-keyVersion, plus each backend's own sub-flags
The default binary links no KMS backend, so it has no --signer-kms-* or --verifier-kms-* flag and rejects them as unknown. spiffe and vault are likewise not compiled into the default release binary; add them via a custom build — see build a custom CI/lock. Run cilock run --help-advanced to see the exact signer flags your binary exposes.
For the full URI conventions, see signing & identity.
cilock plan
Dry-run of
cilock run'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 thefirelist and pass them tocilock 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. |
# 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: one signer unless you pass --countersign.
| 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. |
--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 | Same as an empty --platform-url, and the same opt-out run and verify take: no session lookup, no keyless exchange, no platform TSA. Needs a local signer (-k, or another --signer-* provider this build links: --signer-piv-* in the default binary); without one the command says so, naming only the signers the binary links, instead of failing with "no signers found". |
--countersign | (none) | false | Sign with every configured signer, as on run. |
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 assha256:$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 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. A plain verify treats these digests as search seeds and only notes one that matched nothing. With --vsa-outfile each one also becomes a subject of the signed VSA, so each must be a subject or product of a passed step, or verify fails and writes no VSA. |
--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. Present only in a rookery-builder binary that links a KMS backend; the default binary has none. |
--offline | (none) | Same as an empty --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. Every --signer-* provider you pass signs the VSA, so two providers give a countersigned VSA; with none it is written unsigned. Every VSA subject (-f, --directory-path, each --subjects digest) must be a subject or root-verified product of a passed step; a digest the evidence does not produce, or only consumes as a material, fails the verify and no VSA is written. |
Full verifier flag list is in cilock/internal/options/verify.go.
Exit code 0 on policy pass, 1 when the policy denies, 2 when the verification could not be carried out, 64 on a usage error (see Exit codes).
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 thatcilock bundlebuilds andcilock verify --bundlereads. 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). |
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 \
./appOn success it prints Verified OK and exits 0; any failed check exits non-zero with the reason.
verify-bundle needs sigstore-go, which the default build of cilock, the release binaries included, leaves out. That build keeps the command but refuses every call, exit non-zero, with cilock verify-bundle is not in this build of cilock. Build one that verifies from the rookery cilock module:
GOWORK=off go build -tags sigstore_verify -o cilock ./cmd/cilockcilock bundle
Build or inspect a portable attestation bundle (a tar.gz of DSSE envelopes) — the offline-evidence companion to
cilock verify --bundle.createwalks Archivista's subject graph from a digest and packs everything reachable;inspectprints 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. |
cilock bundle create -s sha256:<digest> -o evidence.tar.gzcilock 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). |
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 --offlinecilock fetch <gitoid>
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.
fetchis 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 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.
# 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.pemcilock attach <repo>@sha256:<hex>
Attach signed attestations to an OCI image: OCI 1.1 referrers by default, the cosign
.atttag when the registry has no referrers API.
Each referrer holds a Sigstore bundle (v0.3). The bundle carries the envelope's RFC 3161 timestamp in its standard field (verificationMaterial.timestampVerificationData). cosign verify-attestation --new-bundle-format reads it, and so does Kyverno verifyImages with type: SigstoreBundle. cosign attach attestation is not a substitute: it re-encodes the envelope and drops signatures[].timestamps, so a policy timestampConstraint fails on what it uploads. Design: docs/design/cilock-oci-attach.md §8a, §8c and §8d in the platform repository.
The image is named by digest. A tag-only reference is refused; a name:tag@sha256:... reference keeps the digest and ignores the tag. Each envelope must be in-toto DSSE, signed, and its statement must name the image digest as a subject. An envelope with more than one signature or with intermediate certificates is refused, because a v0.3 bundle cannot carry them. After the write, attach reads the referrers list until the new referrer is in it.
--to picks the route. auto, the default, uses referrers when the registry serves the OCI referrers API: GET /v2/<name>/referrers/<digest> answers 200 with the OCI index content type, the same test Kyverno's registry library applies. Otherwise it writes the cosign-att route. It also writes to any route that already holds a statement of the same type about the image, so a newer verdict lands wherever an older one can be read. The image manifest must answer first. A 401, 403 or 5xx is an error, never a reason to fall back. An explicit --to never falls back. --to referrers refuses a registry without the API, because an entry in the fallback tag index cannot be removed again.
cosign-att writes the envelope file byte for byte as a layer of the image tagged sha256-<hex>.att, the layout cosign attest writes, so the RFC 3161 token survives. Kyverno type: Cosign and cosign verify-attestation read it. It is a compatibility layout on a mutable tag, safe only with one writer per image at a time: serialize the job per image digest (Jenkins disableConcurrentBuilds(), or a Lockable Resources lock on the digest). A write that sees the tag change under it exits non-zero with concurrent attach detected; re-run.
jfrog-evidence deploys the envelope to JFrog's Evidence service (Artifactory Enterprise+) on the image's manifest.json. It is JFrog-specific: Kyverno does not read it. JFrog looks the signature's keyid up as the alias of a trusted public key and refuses an alias it does not have, so register the key first with cilock attach jfrog-key register. attach checks the alias before it sends anything, adds ;PS256 for an RSA key, and sends only the documented DSSE fields, so JFrog's copy has no RFC 3161 token. Credentials: JFROG_ACCESS_TOKEN with --jfrog-url or JFROG_URL, else the jf CLI's server at that URL, else its default server; a token stored for one server is never sent to another. To resolve the image, the token goes only to the platform's own host or a --jfrog-registry host, so an image reference cannot aim it elsewhere.
Each route's result is printed with the reason it was chosen and the Kyverno verifyImages type that reads it.
| Flag | Default | Description |
|---|---|---|
--envelope <file> | (required) | Signed DSSE envelope to attach. Repeatable. |
--to <route> | auto | auto, referrers, cosign-att, jfrog-evidence, or all (auto plus jfrog-evidence). |
--jfrog-url <url> | $JFROG_URL | JFrog Platform URL for jfrog-evidence. |
--jfrog-key-alias <alias> | the envelope's keyid | Trusted-key alias to send as the keyid. The signature does not cover the keyid, so it stays valid. |
--jfrog-sigstore-bundle | false | Send JFrog a Sigstore bundle instead. It keeps the timestamp, but JFrog does not verify it against a trusted key. |
--jfrog-registry <host> | none | A docker host besides the platform's own that may receive the JFrog token while resolving the image, for Artifactory's subdomain method (e.g. acme-docker-local.jfrog.io). Repeatable. |
--format <fmt> | text | json prints an array of {envelope, route, reason, ref, confirmed, error, kyverno_type, single_writer}, on failure too. |
--replace | false | After the new referrer is listed, delete the image's SLSA verification summaries whose signed timeVerified is strictly older than this one's. Newer or equal ones stay. Kyverno denies an image when any attached summary fails its conditions, so a re-verify uses this. Needs a VSA with a timeVerified no more than 5 minutes ahead of the host clock. Anything it cannot read, order or delete stays, and the command exits non-zero. A summary that is already attached, newer, and trusted (below) makes this one stale, and nothing is written. If this one is not FAILED and an attached summary cannot be read, nothing is written either. |
--policy-emails <email> | none | With --replace: signer emails an attached summary's certificate must name before it can make this one stale. A CA root such as Fulcio issues to anyone, so without this or --policy-uris no root is trusted and no attached summary is newer. Repeatable. |
--policy-uris <uri> | none | With --replace: signer URIs (a CI workflow identity, say) an attached summary's certificate must name, as for --policy-emails. Repeatable. |
--policy-ca-roots <file> | the roots this cilock embeds | With --replace: CA roots an attached summary's signature must chain to before it can make this one stale. Repeatable. |
--policy-ca-intermediates <file> | none | With --replace: intermediates for --policy-ca-roots. Repeatable. |
--policy-timestamp-servers <file> | the authorities this cilock embeds | With --replace: timestamp authority certificate chains an attached summary's RFC 3161 token must verify against. Repeatable. |
Credentials come from the docker keychain (~/.docker/config.json, its credential helpers, podman's auth.json), as for docker pull.
cilock attach registry.example.com/app@sha256:<hex> --envelope vsa.json --replaceFor the referrers route the Kyverno rule sets type: SigstoreBundle on the verifyImages entry. For cosign-att it keeps the default type, Cosign, which reads only the sha256-<hex>.att tag.
cilock attach jfrog-key register
Upload a public key to the JFrog Platform's trusted keys, under the alias
attach --to jfrog-evidencesends as the keyid.
JFrog's Evidence service verifies evidence against the trusted key whose alias equals the signature's keyid. The alias defaults to the key's cilock keyid (cilock keyid show). For a KMS key, whose envelopes carry the KMS URI as the keyid, pass that URI as --alias. Needs an admin token.
| Flag | Default | Description |
|---|---|---|
--public-key <file> | (required) | PEM public key to register. |
--alias <alias> | its cilock keyid | Alias to register it under. |
--jfrog-url <url> | $JFROG_URL | JFrog Platform URL. |
cilock attach jfrog-key register --jfrog-url https://acme.jfrog.io --public-key kms-pub.pem --alias awskms:///alias/provenancecilock download <repo>@sha256:<hex>
Download the attestations
cilock attachwrote to an image, as DSSE envelopes with their RFC 3161 timestamps.
The envelopes are rebuilt from the bundles. The payload, payload type, key id, signature and timestamp tokens come back unchanged; the JSON bytes are new. download does not verify signatures: cilock verify does that. It refuses a bundle referrer whose manifest does not name the image as its subject, or whose statement does not name the image digest. Referrers that are not Sigstore bundles (signatures, SBOMs) are skipped. The envelopes in the image's cosign sha256-<hex>.att tag are read too.
| Flag | Default | Description |
|---|---|---|
--predicate-type <uri> | (all) | Only attestations whose signed statement has this predicate type. |
--outdir <dir> | stdout | Existing directory to write each envelope into, as <sha256 of the envelope>.json. Without it the envelopes go to stdout, one per line. |
cilock download registry.example.com/app@sha256:<hex> --predicate-type https://slsa.dev/verification_summary/v1 --outdir ./evidencecilock attest
Records attestations against the current context without wrapping a command — sugar for
cilock run -- true. Everyrunflag works here. Use it for consultative / at-rest attestors that snapshot state (e.g.github-review,aws-iid) rather than observe a command.
cilock attest -a github-review -k key.pem -o review.bundle.json -s review-headcilock 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 flag applies — signer selection, --outfile, --platform-url, Archivista upload.
# 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/[email protected] \
--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.jsoncilock 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 signcommand rather than running it. Needs a logged-in session with thepolicy:validatescope — if the platform rejects the call for a missing scope, runcilock loginagain 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. |
# 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.jsoncilock 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.jsonsuffix), functionaries populated from each signing keyid, andattestations[]populated from the predicate types found. Edit, then sign withcilock sign. Use--step-prefixto 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. |
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-tsacilock 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-tsaanchors 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, thencilock sign→policy push→policy bind); pass both--productand--tagfor the one-shot derive → sign → push → bind flow. The Archivista query needs a logged-in session; publication needspolicy:publish, and the optional Product binding additionally needs legacypolicy: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. |
# 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 v1cilock 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;
draftsends that source to the platform, which fills in the tenant's platform Fulcio roots and timestamp authorities, validates the result, and returns it.draftthen 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, sodraftnever invokescilock signfor you — it prints the exact command to run. Needs a logged-in session with thepolicy:validatescope; if the platform rejects the call for a missing scope, runcilock loginagain 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. |
# 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 --forcecilock 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 withcilock signagainst the platform's keyless Fulcio. The DSSE upload needsattestation:upload; creating the immutable definition and release needspolicy: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. |
# 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.0cilock policy publish --file --definition --tag
draft+ sign +pushas a single ceremony, for a policy a human signs. It hydrates the hand-authored source against the platform (the same callcilock policy draftmakes), then starts a sign-in whose approval names that exact hydrated document. A person opens the printed URL, reads what the policy requires, and approves with a passkey; the platform signs those exact bytes as that person, never as the caller, and never at a lower assurance level than the session it observed — then creates the PolicyRelease.The caller never holds a key, and the approver 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 pushinstead 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. |
# Publish v1 of judge-gates from a hand-authored source
cilock policy publish -f deploy/pushgate/judge-gates.policy.json -d judge-gates -t v1cilock 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 needspolicy: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. |
# 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-servicecilock 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. |
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 policyfunctionaries[].publickeyidand 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>, matchingsha256sum'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). |
cilock keyid show signer.pub
cilock keyid show signer.key signer.pub other.pem
cilock keyid show --format=json signer.key | jq .cilock kyverno explain
Not in a release yet. Lists the image's referrers, decodes each attached Sigstore bundle, and checks it the way the integrations-catalog Kyverno policy does:
type: SigstoreBundle, a static key,verificationResult == PASSED, andtimeVerifiedwithin--max-age. Kyverno gives one message,no matching signatures found, both when no VSA is attached and when every VSA is signed by another key;explainsays which, and also names aFAILEDVSA, a VSA that is too old, a VSA for another image, and an image over Kyverno's limit of 50 referrers. It mirrors Kyverno: a bundle the trusted keys do not verify is ignored, and every bundle they do verify must pass. Exit0would admit,1would deny,2the registry or a bundle could not be read (never reported as "no VSA"). Registry credentials come from the Docker keychain. It reads the referrers route (the OCI referrers API, or the referrers tag schema where the registry has no API); an image whose VSA was attached as a cosign.atttag is not read yet.
| Flag | Default | Description |
|---|---|---|
<image@sha256:digest> | (required) | The image, named by digest. |
--keys <pem> | (required) | PEM public keys the policy trusts. Repeatable; one file may hold several keys. |
--max-age <duration> | 720h | The policy's freshness window for timeVerified; 0 for a policy with none (platform Enforce's VSA rule). |
--policy-digest <sha256> | (none) | The policy.digest.sha256 the policy pins, if it pins one. |
--format <fmt> | text | text or json. |
cilock kyverno explain registry.example.com/acme/app@sha256:4f… --keys judge-vsa-keys.pemcilock kyverno policy
Not in a release yet.
--keylessprints a KyvernoImageValidatingPolicy(with--namespace, aNamespacedImageValidatingPolicy) that admits a pod only when each image carries aPASSEDVSA signed keyless by the platform, about that image's own digest and, with--policy-digest, about that policy. It checks every container kind (containers,initContainers,ephemeralContainers) on pod create and update, and on thepods/ephemeralcontainerssubresourcekubectl debugwrites to. The trust is inline as a Sigstore trusted root built from the platform's discovery document: the Fulcio chain (signing.trust_bundle_pem) and the TSA chain (signing.tsa_cert_chain_url), with no transparency or CT log, so the policy setsinsecureIgnoreTlogandinsecureIgnoreSCTand Kyverno checks the certificate at the RFC 3161 time. No other policy on the cluster changes. It refuses trust material it cannot render whole: not exactly one self-signed Fulcio root, a certificate that does not chain to it, or a TSA chain without exactly one leaf under one root. Kyverno still needs egress to public-good TUF for a keyless attestor. Attach the VSA withcilock attach --trust-bundlenaming the samesigning.trust_bundle_pem.
| Flag | Default | Description |
|---|---|---|
--keyless | false | Required: the keyless policy is the only one this command renders. |
--platform-url <url> | (required) | The platform whose discovery supplies the trust (https). |
--image <glob> | (required) | Image reference glob the policy applies to. Repeatable. |
--name <name> | judge-keyless-vsa | Policy name. |
--namespace <ns> | (none) | Render a NamespacedImageValidatingPolicy in this namespace. |
--subject <identity> | [email protected] | The signer's certificate subject. |
--issuer <url> | discovery's signing.fulcio_oidc_issuer | The signer's OIDC issuer. |
--action <Deny|Audit> | Deny | validationActions. |
--policy-digest <sha256> | (none; required with Deny) | 64 lowercase hex: the sha256 of the signed policy the PASSED VSA must name as policy.digest.sha256, on that same VSA. Without it a PASSED verdict about any policy admits. |
cilock kyverno policy --keyless --platform-url https://platform.testifysec.com \
--image 'registry.example.com/acme/*' --policy-digest "$POLICY_SHA256" > keyless-vsa.yamlcilock attestors list
Prints a box-drawn table of every attestor compiled into the binary:
┌──────────────────────────┬─────────────────────────────────────────────────────┬─────────────┐
│ 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.
The TYPE column is the type a fresh attestor reports. An attestor registered under more than one type picks one from what it records (github-action signs v0.2 when it records steps, v0.1 otherwise), so --format json adds registered_types listing all of them for those attestors.
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. |
cilock policy guide --goal tests --goal quality
cilock policy guide --topic chain --topic tracecilock 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. |
cilock skill install
cilock skill install --agent codex --scope projectcilock 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. |
cilock policy template --goal tests --goal secrets --goal quality
cilock policy template -p .pushgate/policy.json --add-step docs-build --attestor command-runcilock 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. |
cilock policy prove -p .pushgate/policy.json
cilock policy prove -p .pushgate/policy.json --step app-build -- go build -o bin/app ./cmd/appcilock 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. |
cilock policy input tests.json
cilock policy input tests.json --attestor command-runcilock tools
The catalog of detectors cilock knows how to auto-fire (the same source cilock.dev's tool pages render from).
listenumerates them,showprints one tool's full record,test-planemits 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. |
cilock tools list
cilock tools list --category vulnerability-scan --format jsoncilock 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). |
cilock tools show sarif
cilock tools show sarif --section policy-gotcha
cilock tools show sarif --format jsoncilock 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). 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). |
cilock tools standards
cilock tools standards --format jsoncilock 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. |
cilock tools test-plan
cilock tools test-plan --only sarif --format jsoncilock 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. |
cilock skill install # detect the agent, install for this user
cilock skill install --agent codex --scope project
cilock skill install --dir ./vendor-skillscilock 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.
cilock skill path --agent claude --scope projectcilock 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.
cilock skill show
cilock skill show references/refusals.mdcilock 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. |
# 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 --verifyThe 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 link the same CLI and likewise print the Apache 2.0 statement (the builder's own distribution terms are covered under licensing). Custom binaries built with --customer X --tenant Y additionally show:
Built for: X
Tenant: YThe 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 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.