Skip to main content
View Markdown ↗

Copy this page

Select and copy the Markdown below, then paste it into your LLM.

On this page

cilock CLI reference

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

cilock - Collect and verify attestations about your build environments

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

Top-level commands

CommandPurpose
cilock loginSign in to the platform and store a session bound to a working tenant + product.
cilock useSwitch the working tenant/product the stored session binds attestations to.
cilock whoamiShow the current platform session (tenant, product, expiry).
cilock logoutRemove the stored platform session credential.
cilock enroll agentEnroll 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 / migrateManage 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 doctorRead-only preflight: is the environment sane to attest + upload against the platform?
cilock git configureConfigure Git commit and tag signing through CI/lock's standard X.509 signing protocol.
cilock pushgate statusShow whether the exact current ref and commit were accepted and delivered by Pushgate.
cilock pushgate connectRedeem a Pushgate setup link and push through the gate with the credential kept out of every URL, config value and output.
cilock pushgate disconnectRemove the stored Pushgate push credential and the push URL connect set in this clone.
cilock pushgate credentialGit credential helper for the Pushgate push credential; git runs it, for exactly one repository URL.
cilock node enroll / statusRegister 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 attestRecord attestations without wrapping a command (sugar for run -- true; for consultative/at-rest attestors).
cilock attest vexAuthor 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 verifyVerify an artifact (subject) against a signed policy using attestations as evidence.
cilock verify-bundleVerify 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-bundlesGenerate a starter Witness policy from one or more signed attestation bundles.
cilock policy from-commitAuthor a Witness policy from a commit's CI attestations already in the platform's Archivista.
cilock policy pushUpload a signed policy DSSE to the platform and create a release.
cilock policy bindBind a published policy definition/release to a product on the platform.
cilock policy validateValidate a Witness/cilock policy document (schema; signature only with -k or --require-signed).
cilock policy draftHydrate a hand-authored policy with the tenant's platform trust roots. Returns it UNSIGNED.
cilock keyidPrint 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 --keylessRender 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 / inspectBuild 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 listList 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-planList supported detectors, show one, or emit per-tool test plans.
cilock skill install / path / showInstall, 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 versionPrint the cilock version (--json for version, commit, built, embedded_trust).
cilock --versionPrint 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:

FlagDefaultNotes
--log-level, -l <level>infoOne 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>enforcePolicy-verification hardening: enforce rejects dangerous policy configurations; warn downgrades them to loud warnings. Also settable via CILOCK_POLICY_HARDENING.
--no-colorfalse, or true when NO_COLOR is set and not emptyWrite 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.

CodeMeaning
0Success.
1Failure or refusal: a command that ran and failed, or a policy that denied.
2Verification could not be carried out (cilock verify), as distinct from 1, a policy that denied.
64Usage 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).

FlagDefaultDescription
--platform-url <url>https://platform.testifysec.comPlatform to sign in to.
--token <jwt>(none)JWT for CI/headless login (skips the browser); - reads it from stdin.
--workflow-identityfalseUse the ambient CI workflow OIDC identity (auto-detected on the default platform; required to send a workflow token to a non-default --platform-url).
--interactivefalseForce 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-trustfalseAlso grant the narrow oidc:write scope so this session can run cilock trust. Off by default.
--no-browserfalseNever 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.

FlagDefaultDescription
--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.comPlatform whose session to rebind. Must match the login.
--no-browserfalseNever 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-web

cilock whoami

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

FlagDefaultDescription
--platform-url <url>https://platform.testifysec.comPlatform whose session to show. Must match the login.
--jsonfalseEmit 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 --json

cilock logout

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

FlagDefaultDescription
--platform-url <url>https://platform.testifysec.comPlatform whose session to remove. Must match the login.
cilock logout
cilock logout --platform-url https://platform.example.com

cilock enroll agent

Enroll an agent principal for this machine in one ceremony. A browser window opens on the platform's enrollment page; 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.

FlagDefaultDescription
--platform-url <url>https://platform.testifysec.comPlatform 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 4h

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

cilock agent login

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

FlagDefaultDescription
--platform-url <url>https://platform.testifysec.comPlatform 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.txt

cilock agent logout

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

FlagDefaultDescription
--platform-url <url>https://platform.testifysec.comPlatform whose agent credential to remove. Must match the login.
cilock agent logout --platform-url https://platform.example.com

cilock agent status

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

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

FlagDefaultDescription
--platform-url <url>https://platform.testifysec.comPlatform to report the enrolled agent for. Must match the login.
cilock agent status --platform-url https://platform.example.com

cilock agent migrate

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

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

cilock agent migrate

cilock agent list

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

FlagDefaultDescription
--platform-url <url>https://platform.testifysec.comPlatform whose local agents to list.
cilock agent list --platform-url https://platform.example.com

cilock agent remove

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

FlagDefaultDescription
--platform-url <url>https://platform.testifysec.comPlatform the agent is enrolled with.
cilock agent remove 22222222-2222-2222-2222-222222222222

cilock 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.

FlagDefaultDescription
--platform-url <url>https://platform.testifysec.comPlatform the agent is enrolled with.
cilock agent bearer --platform-url https://platform.example.com | program-that-reads-it

cilock 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.

FlagDefaultDescription
[provider] [owner/repo]auto-detect repoPositional: 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}/archivistaOIDC audience (matches cilock run).
--scope <s>attestation:uploadRepeatable. Only attestation:{upload,read,verify} allowed.
--verifyfalseAlso grant attestation:read (for cilock verify --enable-archivista).
--allowed-ip <cidr>anySource 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 tenantTenant to register the trust under.
--dry-runfalsePrint what would be created without calling the platform.
--yes, -yfalseCreate 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:prod

cilock doctor

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

FlagDefaultDescription
--platform-url <url>https://platform.testifysec.comPlatform to probe.
--jsonfalseEmit 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 --json

cilock git configure

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

FlagDefaultDescription
--globalfalseWrite 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 --global

cilock pushgate status

Read the exact Pushgate delivery record for a Git ref and commit. With no flags, CI/lock discovers the current branch, HEAD commit, selected platform, and the configured Pushgate Git remote. 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.

FlagDefaultDescription
--remote <name>inferred Pushgate remoteSelect a Git remote explicitly when more than one matches the discovered Pushgate origin.
--ref <ref>current branchFully 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>HEADExact lowercase 40-character commit to query.
--waitfalsePoll until delivery succeeds or reaches a terminal failure.
--timeout <duration>15mMaximum time to wait for delivery.
--jsonfalseEmit 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 --json

cilock 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>.git

With 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:

  1. refuses, with nothing sent, unless the store holds a credential for exactly the URL git resolved, after any insteadOf rewrite;
  2. appends an empty credential.helper, then cilock pushgate credential, then credential.useHttpPath=true to the push's command-scope configuration, which git reads after every config file, include, worktree config, GIT_CONFIG_COUNT entry and -c. That push asks CI/lock's helper and no other, with the repository path;
  3. hands over to git's own HTTPS transport.

What connect does, in order:

  1. Discovers the Pushgate origin from the selected platform and refuses a link on any other origin before sending anything.
  2. Checks what it can without the credential: that this is a Git checkout, that git-remote-pushgate is on PATH, and that the store is writable and owner-only.
  3. 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.
  4. Stores the credential, keyed by the exact repository URL. Connecting again replaces it.
  5. Sets the push URL. If that fails, the credential stays stored and connect prints the one git command to run.
  6. 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 as remote.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.

FlagDefaultDescription
--platform-url <url>selected loginThe 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 HEAD

cilock 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.git

cilock 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.

OperationWhat it does
getPrints 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, eraseRead 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 get

cilock 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.

FlagDefaultDescription
--tenant <id>(required)Tenant the EK was approved in.
--platform-url <url>https://platform.testifysec.comPlatform to register with.
--tpm-device <path>/dev/tpmrm0TPM device (the kernel resource manager).
--state-dir <dir>/var/lib/cilock-nodePrivate 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 status

cilock run [cmd]

Runs the provided command and records attestations about the execution.

Always-run attestors (cannot be omitted): material, product, and (when args are provided) command-run. 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

FlagShortDefaultDescription
--step <name>-sinferredStep category. Optional — when omitted, inferred from the wrapped command. Must be a value from the step lexicon.
--attestations <list>-aenvironment,git,platformComma-separated attestors (product + material are always recorded). Passing -a disables auto-detection (set becomes exact) unless --workload auto.
--workingdir <dir>-dcurrent dirWorking directory for material/product capture.
--outfile <path>-ostdoutPath for the signed DSSE envelope.
--summary <mode>(none)short in CI, else fullHow 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)falsePrint 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-rfalseEnable syscall tracing (Linux). Backend is ptrace+seccomp or eBPF — see capture modes. No-op on non-Linux.
--ignore-command-exit-code(none)falseExit 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)sha256Hash algorithms used in digests (comma-separated).
--max-attestation-bytes <size>(none)4MiBLargest 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)falseAllow 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)falsePush the signed envelope to Archivista.
--archivista-server <url>(none)https://platform.testifysec.com/archivistaArchivista server URL (derived from --platform-url if not explicitly set).
--archivista-headers <h>(none)(none)Repeatable Authorization: ... headers for Archivista.
--archivista-oidc(none)falseUse GitHub Actions OIDC for Archivista auth (auto-enabled in GitHub Actions).
--archivista-audience <aud>(none)Archivista server URLOIDC audience claim.
--platform-url <url>(none)https://platform.testifysec.comTestifySec platform URL; archivista, fulcio, and TSA URLs are derived from it if unset.
--countersign(none)falseSign 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)falseRemove 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)falseDisable CI/lock's default sensitive-var list entirely.

Capture, detection & hardening flags

FlagDefaultDescription
--capture-mode <mode>autoWhere material/product digests come from: auto (trace if --trace, else walk), walk, trace (requires --trace), ima. See capture modes.
--hardening <profile>standardIntegrity profile: off, standard (fanotify on, fs-verity opportunistic), strict (fanotify + fs-verity required, drops fail).
--require-zero-dropsfrom --hardeningFail the run if the trace dropped any event. strict ⇒ true.
--workload <mode>autoAttestor selection. auto detects (only when -a absent, unless forced); manual uses -a/defaults exactly. See auto-detection.
--validate-onlyfalseRun 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-manifestfalsePublish 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.
--diagnosefalseVerbose 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-path
  • fulcio: --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, default SIGSTORE_ID_TOKEN), --signer-fulcio-use-http (default true)
  • 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 (default sha256), --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 the fire list and pass them to cilock run -a <attestor>,... to run the planned set.

FlagDefaultDescription
-- <command> [args...](required)The command to plan for (after the -- separator).
--format <fmt>texttext or json (machine-readable, for an agent to consume).
--verbose, -vfalseInclude 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.

FlagShortDefaultDescription
--infile <path>-f(required)File to sign (typically the policy JSON).
--outfile <path>-ostdoutDestination for the signed DSSE envelope.
--datatype <uri>-thttps://witness.testifysec.com/policy/v0.1DSSE 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)4MiBLargest 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.comPlatform whose session is exchanged for a keyless Fulcio certificate. Pass "" to sign with --signer-* only.
--offline(none)falseSame 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)falseSign 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 as sha256:$DIGEST (-s) — and CI/lock uses the supplied attestations as the evidence that validates it. You verify the thing, not the attestation.

Because v0.3 product/material attestations inline their Merkle leaves 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.

FlagShortDescription
--policy <path>-pPath to the signed DSSE policy envelope.
--publickey <path>-kPath to the policy signer's public key (PEM).
--attestations <list>-aAttestation envelope files (comma-separated; repeatable).
--artifactfile <path>-fPath to the artifact subject to verify.
--subjects <list>-sAdditional 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 that cilock bundle builds and cilock verify --bundle reads. The flags are the sigstore-conformance CLI protocol's.

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

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

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

FlagDefaultDescription
--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 TUFPath to a trusted_root.json.
--stagingfalseUse 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 \
  ./app

On 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/cilock

cilock bundle

Build or inspect a portable attestation bundle (a tar.gz of DSSE envelopes) — the offline-evidence companion to cilock verify --bundle. create walks Archivista's subject graph from a digest and packs everything reachable; inspect prints a bundle's manifest so you can see what's inside before verifying.

cilock bundle create

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

FlagDefaultDescription
--subject, -s <digest>(required)Subject digest(s) to seed the graph walk (e.g. sha256:abc...). Repeatable.
--output, -o <path>stdoutPath to write the bundle (tar.gz).
--max-depth <n>5Maximum subject-graph traversal depth.
--max-envelopes <n>10000Maximum envelopes to fetch before aborting.
cilock bundle create -s sha256:<digest> -o evidence.tar.gz

cilock bundle inspect

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

FlagDefaultDescription
<bundle.tar.gz>(required)Bundle to inspect.
--jsonfalseEmit 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 --offline

cilock fetch <gitoid>

Not in CI/lock 4.4.0

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

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

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

What this verifies, and what it does not. fetch verifies the content address: the bytes it writes are re-hashed locally and must equal the gitoid you asked for, so a compromised or on-path Archivista cannot hand you different evidence under the name you requested. It does not verify the signature, and it does not verify who signed. An envelope that downloads cleanly may be signed by anyone, or carry a signature that does not validate at all — cilock verify 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".

FlagDefaultDescription
<gitoid>...(required)One or more gitoids to download. More than one requires --outdir.
--outfile, -o <path>stdoutPath 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.
--payloadfalseWrite the base64-decoded DSSE payload (the in-toto statement) instead of the envelope.
--predicatefalseWrite only the statement's predicate member instead of the envelope.
--forcefalseOverwrite the output if it already exists.
--platform-url <url>logged-in platformPlatform whose Archivista to read, and whose session authorizes the read.
--archivista-url <url>the platform's own ArchivistaThird-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.pem

cilock attach <repo>@sha256:<hex>

Attach signed attestations to an OCI image: OCI 1.1 referrers by default, the cosign .att tag 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.

FlagDefaultDescription
--envelope <file>(required)Signed DSSE envelope to attach. Repeatable.
--to <route>autoauto, referrers, cosign-att, jfrog-evidence, or all (auto plus jfrog-evidence).
--jfrog-url <url>$JFROG_URLJFrog Platform URL for jfrog-evidence.
--jfrog-key-alias <alias>the envelope's keyidTrusted-key alias to send as the keyid. The signature does not cover the keyid, so it stays valid.
--jfrog-sigstore-bundlefalseSend JFrog a Sigstore bundle instead. It keeps the timestamp, but JFrog does not verify it against a trusted key.
--jfrog-registry <host>noneA 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>textjson prints an array of {envelope, route, reason, ref, confirmed, error, kyverno_type, single_writer}, on failure too.
--replacefalseAfter 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>noneWith --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>noneWith --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 embedsWith --replace: CA roots an attached summary's signature must chain to before it can make this one stale. Repeatable.
--policy-ca-intermediates <file>noneWith --replace: intermediates for --policy-ca-roots. Repeatable.
--policy-timestamp-servers <file>the authorities this cilock embedsWith --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 --replace

For 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-evidence sends 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.

FlagDefaultDescription
--public-key <file>(required)PEM public key to register.
--alias <alias>its cilock keyidAlias to register it under.
--jfrog-url <url>$JFROG_URLJFrog Platform URL.
cilock attach jfrog-key register --jfrog-url https://acme.jfrog.io --public-key kms-pub.pem --alias awskms:///alias/provenance

cilock download <repo>@sha256:<hex>

Download the attestations cilock attach wrote 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.

FlagDefaultDescription
--predicate-type <uri>(all)Only attestations whose signed statement has this predicate type.
--outdir <dir>stdoutExisting 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 ./evidence

cilock attest

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

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

cilock attest vex

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

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

FlagDefaultDescription
--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>cilockAuthor 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.jsonWhere 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.json

cilock policy draft

Turns a hand-authored policy source into a complete, verifiable policy document without you looking up a trust root. You write the part that expresses intent — the steps, the functionaries, the rego — and leave the trust material out; draft sends that source to the platform, which fills in the tenant's platform Fulcio roots and timestamp authorities, validates the result, and returns it. draft then verifies the returned digest against the bytes it actually received before writing to --output.

draft never signs. The hydrated document is inert until a human signs it, and that is deliberate: signing is what makes a policy authoritative, so it stays an explicit human act with a human identity. draft prints the exact cilock sign command rather than running it. Needs a logged-in session with the policy:validate scope — if the platform rejects the call for a missing scope, run cilock login again to pick it up.

FlagDefaultDescription
--datatype, -t <uri>https://aflock.ai/policy/v0.1Policy payload type sent for hydration.
--file, -f <path>(required)Path to the hand-authored policy source.
--forcefalseOverwrite --output if it already exists.
--output, -o <path>the source path with a .hydrated.json suffixWhere to write the hydrated, UNSIGNED policy.
--platform-url <url>the logged-in platformTestifySec 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

cilock policy from-bundles

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

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

FlagDefaultDescription
--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 platformPlatform 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-tsafalseAnchor 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-tsa

cilock policy from-commit <commit-sha>

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

FlagDefaultDescription
--definition, -d <name>the product namePolicyDefinition 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 platformTestifySec 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-tsafalseAnchor 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 v1

cilock policy draft --file

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

FlagDefaultDescription
--file, -f <path>(required)Path to the hand-authored policy source.
--output, -o <path>the source path with a .hydrated.json suffixWhere to write the hydrated, UNSIGNED policy.
--forcefalseOverwrite --output if it already exists.
--datatype, -t <url>https://aflock.ai/policy/v0.1Policy payload type sent for hydration.
--platform-url <url>the logged-in platformTestifySec 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 --force

cilock policy push --file --definition --tag

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

FlagDefaultDescription
--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 platformTestifySec 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.0

cilock policy publish --file --definition --tag

draft + sign + push as a single ceremony, for a policy a human signs. It hydrates the hand-authored source against the platform (the same call cilock policy draft makes), then starts a sign-in whose approval names that exact hydrated document. 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 push instead when the policy is already DSSE-signed by an author key.

The release is created Off. Turning it on for a repository stays a separate act on Pushgate.

FlagDefaultDescription
--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 platformTestifySec platform URL.
--datatype <type>https://aflock.ai/policy/v0.1Policy 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 v1

cilock policy bind --definition --product

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

FlagDefaultDescription
--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 platformTestifySec platform URL.
--dry-runfalseResolve 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-service

cilock policy bind pushgate

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

FlagDefaultDescription
--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>warnMode the human should choose in the review.
--reason <text>(none)Reason to show in the review (the human types the final one).
--jsonfalseEmit one JSON object (repository, release, mode, reason, review_url, opened, applied). applied is always false.
--platform-url <url>the selected loginPlatform 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 policy functionaries[].publickeyid and in attestation signatures. Reads PEM public keys (PKIX) or private keys (PKCS#8/PKCS#1/SEC1; the public half is extracted). One line per input (<keyid> <path>, matching sha256sum's shape). Keys come from positional args or -k/--key.

FlagDefaultDescription
<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>texttext = 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, and timeVerified within --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; explain says which, and also names a FAILED VSA, 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. Exit 0 would admit, 1 would deny, 2 the 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 .att tag is not read yet.

FlagDefaultDescription
<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>720hThe 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>texttext or json.
cilock kyverno explain registry.example.com/acme/app@sha256:4f… --keys judge-vsa-keys.pem

cilock kyverno policy

Not in a release yet. --keyless prints a Kyverno ImageValidatingPolicy (with --namespace, a NamespacedImageValidatingPolicy) that admits a pod only when each image carries a PASSED VSA 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 the pods/ephemeralcontainers subresource kubectl debug writes 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 sets insecureIgnoreTlog and insecureIgnoreSCT and 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 with cilock attach --trust-bundle naming the same signing.trust_bundle_pem.

FlagDefaultDescription
--keylessfalseRequired: 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-vsaPolicy 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_issuerThe signer's OIDC issuer.
--action <Deny|Audit>DenyvalidationActions.
--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.yaml

cilock 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.

FlagShortDefaultDescription
--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)falseFail unless the policy is a DSSE envelope with at least one signature (presence only; add -k to verify it).
--strict(none)falseAlso fail on the empty platform trust placeholders an unsigned draft carries.
--format <fmt>(none)texttext 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).

FlagShortDefaultDescription
--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)texttext or json.
cilock policy guide --goal tests --goal quality
cilock policy guide --topic chain --topic trace

cilock skill

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

cilock skill install

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

FlagDefaultDescription
--agent <name>autoclaude, codex, opencode, or auto (detect from the environment).
--scope <scope>useruser (every project on this machine) or project (this repository only).
--dir <path>(none)Install into <dir>/pushgate instead of the agent's skills directory.
--forcefalseReplace files cilock did not write, or that were edited since.
cilock skill install
cilock skill install --agent codex --scope project

cilock skill path

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

cilock skill show [file]

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

cilock policy template

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

FlagDefaultDescription
--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).
--forcefalseOverwrite 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-run

cilock policy prove

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

FlagShortDefaultDescription
--policy <path>-p.pushgate/policy.jsonThe 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)falseRecord every step with --trace.
--platform-url <url>(none)(the cilock default platform)Platform whose enrolled agent the functionary names.
--no-normalize(none)falseProve 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/app

cilock policy input <envelope.json>

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

FlagDefaultDescription
--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-run

cilock tools

The catalog of detectors cilock knows how to auto-fire (the same source cilock.dev's tool pages render from). list enumerates them, show prints one tool's full record, test-plan emits a validation plan.

cilock tools list

List every detector cilock knows how to auto-fire.

FlagDefaultDescription
--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>tabletable or json.
cilock tools list
cilock tools list --category vulnerability-scan --format json

cilock tools show <name>

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

FlagDefaultDescription
<name>(required)Tool/attestor to show (e.g. sarif).
--section <slug>(all)Print only one documentation section, by slug (see the summary).
--format <fmt>texttext or json (the full machine-readable record).
cilock tools show sarif
cilock tools show sarif --section policy-gotcha
cilock tools show sarif --format json

cilock tools standards

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

FlagDefaultDescription
--format <fmt>texttext or json (the catalog the docs page generates from).
cilock tools standards
cilock tools standards --format json

cilock tools test-plan

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

FlagDefaultDescription
--only <name>(all)Limit the plan to a single detector.
--format <fmt>markdownmarkdown or json.
cilock tools test-plan
cilock tools test-plan --only sarif --format json

cilock skill

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

cilock skill install

Write the skill where the chosen agent discovers skills:

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

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

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

FlagDefaultDescription
--agent <name>autoclaude, codex, opencode, or auto (detect from the environment).
--scope <scope>useruser (every project on this machine) or project (this repository only).
--dir <path>(none)Install into <dir>/pushgate instead of the agent's skills directory.
--forcefalseReplace 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-skills

cilock skill path

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

cilock skill path --agent claude --scope project

cilock skill show

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

cilock skill show
cilock skill show references/refusals.md

cilock get <tool>

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

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

FlagDefaultDescription
<tool>(required)Tool to install. Passing an unknown name fails and prints the available tools (today: syft, zarf).
--dest <dir>$HOME/.cilock/binInstallation directory.
--verifyfalseHash 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 --verify

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

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

cilock completion <shell>

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

cilock version

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

cilock license

Prints the license under which this binary is distributed, plus any branded-distribution metadata baked in at build time. The stock cilock CLI is Apache 2.0, so a stock binary shows the Apache 2.0 statement. Binaries produced by the rookery-builder 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:    Y

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

Configuration

CI/lock is args-only: there is no config file. CLI flags (highest precedence), a small set of CILOCK_* env vars, and built-in defaults are the entire configuration surface. See Configuration 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.