cilock

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.

Three trust postures, one verifier
TestifySec platform PKI
Released binaries carry the platform's trust compiled in — CA roots, TSA roots, and the policy-signer identity. Verification of platform-signed policies needs no trust flags at all.
Customer-run infrastructure
Your own platform deployment embeds Fulcio and a TSA, derived from a single root key you hold. No public certificate authority is in the trust path — this is the disconnected-environment posture.
Public Sigstore
Point the same trust flags at the public Sigstore trust roots and OIDC issuer for teams already invested in the public-good infrastructure.
The policy schema, the evaluation engine, and the verdict are identical in all three — only where trust comes from changes.

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 version reports 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 the CILOCK_NO_EMBEDDED_TRUST environment variable) makes a released binary behave like a stock build, requiring explicit --policy-ca-roots and 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's https://oauth2.sigstore.dev/auth is 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.gz loads attestation envelopes from a portable bundle file instead of querying an evidence store. Bundles are produced by cilock bundle create, or by passing --output-bundle during 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…PostureTrust comes from
Using the hosted platform with a released cilock binaryPlatform PKITrust compiled into the binary; discovery, trust-on-first-use pinned
Running your own platform, including air-gappedCustomer-runYour platform's discovery document, rooted in a key you hold
Invested in public SigstorePublic SigstorePublic roots and issuer, supplied via the --policy-* trust flags
Verifying with no network at allAny, offlineExplicit 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.