# CI/lock — trust models

Source: https://www.testifysec.com/docs/cilock/trust/

The three trust postures for cilock verification: TestifySec platform PKI, customer-run infrastructure, and public Sigstore — plus signed vs key-signed policies and fully offline verification.

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

 

![CI/lock runs the configured work and records observations. A configured identity signs the resulting attestation. Storage preserves the record, and verification separately checks signatures and policy requirements. Successful storage is not a policy pass.](https://www.testifysec.com/media/docs/generated/cilock-evidence.svg)

**Capture, sign, store, verify.** CI/lock runs the configured work and records observations. A configured identity signs the resulting attestation. Storage preserves the record, and verification separately checks signatures and policy requirements. Successful storage is not a policy pass.

**Diagram source (Mermaid)**

```mermaid
sequenceDiagram
  participant W as Workflow
  participant C as CI/lock
  participant S as Signing authority
  participant E as Evidence store
  participant V as Verifier
  W->>C: Run configured work
  C->>C: Record observed results and subjects
  C->>S: Request authorized signing
  S-->>C: Signed attestation
  C->>E: Store evidence when configured
  E-->>V: Retrieve signed record
  V->>V: Check signer, subject, and policy
```

 

## Can an agent fabricate evidence?

 

A signature binds bytes to a signer; it does not make the signed statement true. The accepted producer, required checks, execution environment and signing authority remain part of the trust boundary. Read the shared [trust model](https://www.testifysec.com/docs/concepts/trust-model) for fabrication cases, independent collection requirements and limits.

 

This page focuses on how the verifier obtains policy trust. Keep policy trust separate from the attestation requirements inside the trusted policy.

 

## Posture one: TestifySec platform PKI

 

CI/lock releases may carry the platform's policy-signing trust compiled in; inspect `cilock version` for the actual build. That embedded configuration includes: 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:

 

```bash
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](https://www.testifysec.com/docs/pushgate/trust-architecture) page.

 

Verification points at your platform instead of the hosted one:

 

```bash
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):

 

```bash
cilock verify ./artifact -p policy.signed.json \
  --policy-ca-roots fulcio-root.pem \
  --policy-emails security-team@example.com
```

 

**Key-signed policy** — signed with a long-lived key you manage. Verify with `-k`, the policy signer's public key:

 

```bash
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.
 - **`--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. On `verify` it is the same as an empty `--platform-url`, but prefer the flag: `cilock login`, `whoami`, `use` and `doctor` read an empty URL as the hosted platform, not as "no platform".

 

```bash
# Fully offline: policy key, evidence bundle, no network.
cilock verify -p policy.json -k policy-pub.pem \
  --bundle evidence.tar.gz --offline
```

 

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.
