cilock — trust models
cilock is the CLI that wraps a build step, records signed attestations about it, and verifies an artifact against a witness policy. Every verification answers two trust questions, and cilock keeps them deliberately separate:
- Policy trust — who is allowed to sign the policy itself. This is the bootstrap decision: until the policy's signature is trusted, nothing the policy says counts.
- Attestation trust — whose signatures count as evidence for each step. This is defined inside the now-trusted policy: its roots, timestamp authorities, and public keys name exactly which identities may sign which step.
Because attestation trust always comes from the policy, choosing a trust posture is really choosing where policy trust comes from. There are three postures, and the verifier, the policy schema, and the verdict are identical across them.
Posture one: TestifySec platform PKI
Released cilock binaries are built with the platform's policy-signing trust compiled in: the platform's Fulcio CA roots, its TSA roots, and the identity allowed to sign platform policies. A binary like that verifies a platform-signed policy with no trust flags at all:
cilock verify ./artifact -p policy.signed.json --attestations build.att.json
Three properties worth knowing:
- The embedded trust pins only policy trust, never attestation trust. What counts as evidence is still decided by the policy you pass, not by anything baked into the binary.
- It is auditable.
cilock versionreports which platform's trust a binary carries, so a prod-trusting binary and a staging-trusting binary are distinguishable. - It is escapable.
--no-embedded-trust(or theCILOCK_NO_EMBEDDED_TRUSTenvironment variable) makes a released binary behave like a stock build, requiring explicit--policy-ca-rootsand identity flags.
When a logged-in cilock verify talks to a platform instead of relying on embedded trust, it derives the trust anchors from the platform's discovery document (/.well-known/judge-configuration). That network-served bundle is trust-on-first-use pinned per platform: once pinned, a silently changed bundle is refused, and an operator must pass --trust-discovery to acknowledge a genuine rotation. An explicit --policy-ca-roots always wins over anything discovered or embedded.
Posture two: customer-run infrastructure
You can run the entire trust root yourself. The standalone platform binary embeds Fulcio, an RFC 3161 timestamp authority, the evidence store, and identity — all deriving from a single P-256 root key on your disk. No public certificate authority and no hosted timestamp service is anywhere in the trust path, which makes this the posture for restricted and disconnected environments. The full key architecture is on the trust architecture and PKI page.
Verification points at your platform instead of the hosted one:
cilock verify ./artifact -p policy.signed.json --platform-url https://platform.internal.example
Endpoint and trust derivation work exactly as in posture one — discovery document, trust-on-first-use pinning, --trust-discovery on rotation — except the platform answering is yours. Your policies are signed by your identities against your CA, and nothing about verification depends on TestifySec's infrastructure being reachable, or existing.
Posture three: public Sigstore
For teams already invested in public Sigstore, cilock's verification is deliberately CA-agnostic x.509: the same flags that carry platform trust carry public-good trust instead.
- Supply the public Sigstore Fulcio root via
--policy-ca-roots(and intermediates via--policy-ca-intermediates). - Set the expected certificate issuer with
--policy-fulcio-oidc-issuer— the default is GitHub Actions' issuer, and public Sigstore'shttps://oauth2.sigstore.dev/authis the documented override for keyless flows outside GitHub Actions. - Constrain who may sign with the identity flags:
--policy-emails,--policy-uris, or the Fulcio extension flags such as--policy-fulcio-source-repository-uri, which pin a keyless signer to a repository or workflow. - Keyless signing against a Fulcio instance of your choosing works the same way: the Fulcio signer takes the CA's URL, so it can be pointed at public Sigstore's Fulcio rather than a platform's.
Stated honestly, per this documentation's habit: what ships today is bring-your-own-roots support — you point cilock's trust flags at public Sigstore's published roots and issuer, and verification proceeds like any other x.509 posture. cilock does not yet fetch or refresh the public Sigstore trust root via TUF, and it does not verify Rekor transparency-log inclusion proofs; timestamp verification is RFC 3161 against a chain you supply. Teams whose requirements hinge specifically on TUF-distributed roots or transparency-log proofs should treat those as planned rather than shipped.
Signed policies: keyless or key-signed
A policy is itself a signed DSSE envelope, and cilock accepts two signing regimes for it:
Keyless (x.509) policy — signed via Fulcio with a short-lived certificate. Verify with CA roots plus identity constraints (or with embedded/discovered trust supplying both):
cilock verify ./artifact -p policy.signed.json \
--policy-ca-roots fulcio-root.pem \
--policy-emails [email protected]
Key-signed policy — signed with a long-lived key you manage. Verify with -k, the policy signer's public key:
cilock verify -p policy.json -k policy-pub.pem \
-a build.att.json -a test.att.json
The policy-signing key deserves more care than any attestation key: whoever signs the policy defines what counts as acceptable evidence for everything downstream. That is why, in every posture, the policy signer is the identity you constrain hardest — and why it should be a key or identity you control, not your platform operator.
Offline and air-gapped verification
Verification does not require a platform connection. Two flags make the offline story complete:
--bundle evidence.tar.gzloads attestation envelopes from a portable bundle file instead of querying an evidence store. Bundles are produced bycilock bundle create, or by passing--output-bundleduring an earlier verify — which writes every envelope that verification loaded into one portable evidence package for later offline re-verification.--platform-url ""(or its clearer alias,--offline) opts out of the platform entirely: no evidence-store lookup, no discovery fetch, no platform-derived timestamp trust. All trust comes from explicit--policy-*flags or from the binary's embedded trust.
# Fully offline: policy key, evidence bundle, no network.
cilock verify -p policy.json -k policy-pub.pem \
--bundle evidence.tar.gz --platform-url ""
This is the disconnected-environment end state: a policy, its signer's key or CA, and a bundle of evidence verify on a machine with no network at all, and produce the same signed verdict they would anywhere else.
Choosing a posture
| You are… | Posture | Trust comes from |
|---|---|---|
| Using the hosted platform with a released cilock binary | Platform PKI | Trust compiled into the binary; discovery, trust-on-first-use pinned |
| Running your own platform, including air-gapped | Customer-run | Your platform's discovery document, rooted in a key you hold |
| Invested in public Sigstore | Public Sigstore | Public roots and issuer, supplied via the --policy-* trust flags |
| Verifying with no network at all | Any, offline | Explicit flags or embedded trust, plus a --bundle evidence package |
The postures compose rather than conflict: a policy signed against your own platform can be verified offline from a bundle, and a team can hold its policy-signing key in its own HSM while its attestations are signed keylessly. The constant is that the verdict is only as good as the policy trust you started from — so start from the one you actually control.