Skip to main content
View Markdown ↗

Copy this page

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

On this page

Connect to the platform

Your first attestation signed and verified evidence entirely on your laptop with a local key. That's the whole loop, offline. Connecting to the TestifySec platform adds three things a local key can't:

  • Keyless signing — sign against the platform's Fulcio with your identity, no key file to manage or leak.
  • Storage + sharing — attestations upload to the platform's Archivista, so policy verification, dashboards, and teammates can find them by the artifact's digest.
  • CI trust — let a GitHub/GitLab pipeline upload keylessly, without a long-lived secret.

The one thing to understand: signing needs no login, but uploading does. The platform derives Fulcio (signing), TSA (timestamping), and Archivista (storage) from a single --platform-url — but storing an attestation has to bind it to your tenant and product, and that binding is what a session carries.

0. Start where the first page ended

Work in the same demo-cilock directory. From Your first attestation you keep:

  • main.go, go.mod and the built myapp: the build this page attests again.
  • testkey.pem and testpub.pem: a local scratch key. It stays useful for offline checks, but nothing on this page trusts it.
  • build.attestation.json, policy.json and policy-signed.json: the local loop's evidence and policy. They trust only testpub.pem, so they cannot accept keyless evidence. This page writes a new policy beside them and does not change them.

Set the platform you are connecting to, and check it before you use it. Paste this into the shell you will use for the rest of the page:

PLATFORM=https://platform.testifysec.com   # or your self-hosted platform's https:// URL
case "$PLATFORM" in
  https://?*) echo "platform: $PLATFORM" ;;
  *) echo "PLATFORM is not an https:// URL: '$PLATFORM'. Stopping before any command below runs." >&2; exit 2 ;;
esac

If the value is wrong, the check exits that shell, so no later command can run against the wrong platform. Open a new terminal, fix the URL and paste the block again. The check only proves the value looks like an https:// origin, not that it is a platform your organization approved.

Every command below passes --platform-url "$PLATFORM". Keep it on each one. Without it, login, whoami, use, doctor and trust talk to the hosted platform, not a self-hosted one you logged in to. With an empty value, run, sign and verify turn the platform off, as --offline did on the first page.

1. Log in

cilock login --platform-url "$PLATFORM"

This opens a browser approve page and stores a session credential. The approve page binds a working tenant and product, creating a default tenant and product if you have none yet, so every attestation you upload is scoped to one.

Confirm it worked:

cilock whoami --platform-url "$PLATFORM"

It prints the platform, the tenant, the bound product, your email: and the session expiry. That email is the identity in the certificate your keyless signatures carry; step 5 writes it into the policy.

Three identities can sign on this page and the next ones, and a policy trusts each one separately:

  • You, signed in with cilock login: a certificate naming your email. This page uses it.
  • An enrolled agent (cilock enroll agent, approved by a human in the browser): a certificate naming the agent. If cilock whoami says the machine signs as an enrolled agent, your runs sign as that agent, not as you, and the policy in step 5 must name the agent instead.
  • A CI workload (step 6): a certificate naming the pipeline.

The local testkey.pem from the first page is a fourth, and none of the three above is it.

2. (Optional) switch the working scope

If you belong to more than one tenant or want attestations under a different product, switch the binding — the cilock analog of kubectl config use-context:

# Pick or create a tenant/product interactively
cilock use --platform-url "$PLATFORM"

# Or bind a known product directly (no browser)
cilock use --platform-url "$PLATFORM" --product-id <uuid> --product-name acme-web

cilock run then scopes every attestation to that product without re-prompting.

3. Preflight before a real run

Before a multi-minute build, confirm signing and upload will actually work — instead of discovering a misconfiguration afterward:

cilock doctor --platform-url "$PLATFORM"

It prints a green/red checklist: logged in? platform reachable? Fulcio / TSA / Archivista resolved? upload authorized? Pass --json for a machine-readable report (report.ok) an agent can gate on.

4. Run with upload

Now the same cilock run you used locally, but keyless and uploaded:

cilock run \
  --step build \
  --platform-url "$PLATFORM" \
  --enable-archivista \
  -- go build -o myapp ./

cilock signs keyless against the platform Fulcio, timestamps against its TSA, and uploads the signed DSSE envelope to your tenant's Archivista. The run summary prints the gitoid — the address other tools (and cilock verify --enable-archivista) use to retrieve the evidence by subject digest, no local bundle files required.

If the upload is rejected with a 401 / Invalid API credential, your session expired or the run targeted a platform you're not logged in to — re-run cilock login (or cilock doctor to see exactly which check fails).

5. Verify against a policy that trusts you

The first page's policy-signed.json trusts only the local key, so it refuses this keyless build. Write a policy whose functionary is you, as the platform's Fulcio certifies you, and leave its trust roots empty for the platform to fill:

policy-platform.json
{
  "expires": "2030-12-17T23:57:40-05:00",
  "steps": {
    "build": {
      "name": "build",
      "attestations": [
        {"type": "https://aflock.ai/attestations/material/v0.3", "regopolicies": []},
        {"type": "https://aflock.ai/attestations/command-run/v0.2", "regopolicies": []},
        {"type": "https://aflock.ai/attestations/product/v0.3", "regopolicies": []}
      ],
      "functionaries": [{
        "type": "root",
        "certConstraint": {
          "commonname": "*",
          "dnsnames": ["*"],
          "emails": ["__YOUR_EMAIL__"],
          "organizations": ["*"],
          "uris": ["*"],
          "roots": ["fulcio-root"],
          "extensions": {"issuer": "__PLATFORM__/fulcio/oidc"}
        }
      }]
    }
  },
  "roots": {"fulcio-root": {"certificate": ""}},
  "timestampauthorities": {"platform-tsa": {"certificate": ""}}
}

Fill in your email (from cilock whoami) and the platform, and check both before writing:

EMAIL=$(cilock whoami --platform-url "$PLATFORM" | awk '$1 == "email:" {print $2}')
if printf '%s' "$EMAIL" | grep -Eq '^[^@ ]+@[^@ ]+

    
    

    
    
    
    
  

 && [ -n "$PLATFORM" ]; then
  sed -e "s|__YOUR_EMAIL__|${EMAIL}|g" -e "s|__PLATFORM__|${PLATFORM}|g" \
    policy-platform.json > policy-platform.json.tmp && mv policy-platform.json.tmp policy-platform.json
  echo "policy-platform.json trusts $EMAIL, certified by $PLATFORM"
else
  echo "No email from 'cilock whoami'. Log in (step 1) as a person; an enrolled agent has no email." >&2
fi

Let the platform fill the trust roots. cilock policy draft puts the platform's Fulcio CA chain into fulcio-root and its timestamp authority into platform-tsa, checks the result, and writes it unsigned:

cilock policy draft -f policy-platform.json -o policy-platform.hydrated.json --platform-url "$PLATFORM"

Sign it yourself. This is the human act that makes the policy authoritative, and cilock never does it for you. --human opens your browser and signs keyless as the person who logs in there, never with a stored session or an enrolled agent's credential:

cilock sign --human -f policy-platform.hydrated.json -o policy-platform-signed.json --platform-url "$PLATFORM"

cilock then warns that the policy is signed by an email identity rather than a CI workflow identity. That is expected here: a person signed it. It is also why the verify below names you as the policy signer.

Verify with that exact signed file, naming yourself as the policy's signer:

cilock verify ./myapp -p policy-platform-signed.json --policy-emails "$EMAIL" \
  --platform-url "$PLATFORM" --enable-archivista

--policy-emails is needed because, by default, verify trusts a policy signed by a CI workflow identity, not by a person. The issuer it checks still comes from the platform's discovery document. --enable-archivista lets verify retrieve the attestations by the artifact's subject digest, so the upload from step 4 is what it checks. On success the exit code is 0 and the result line reads verified: sha256:<digest> bound to step "build" …, as on the first page. A build signed by anyone else (the first page's local key, an enrolled agent, another person) is refused, because the policy names only you.

Not run end to end in these docs' tests

The offline parts of this step are tested: the PLATFORM check, and that policy-platform.json with your email filled in is a policy cilock policy draft accepts and hydrates. The keyless run, the upload, the platform's hydration, your signature and the stored verification need a signed-in session and are not run by the tests.

6. Let CI upload (trust a pipeline)

Your laptop session can't sign for a CI job — but you don't want a long-lived secret in CI either. Instead, register the pipeline's OIDC identity as a trusted uploader, once, as a tenant admin:

# Re-authenticate with the trust opt-in, then trust the repo's Actions
cilock login --platform-url "$PLATFORM" --allow-trust
cilock trust github your-org/your-repo --platform-url "$PLATFORM"

cilock trust creates an OIDC federated credential (never a stored secret): it tells the platform to trust attestations signed by that repo's GitHub Actions OIDC identity, for the same ${platform}/archivista audience cilock run uploads to. After that, a workflow can upload keylessly with enable-archivista: true — which is exactly the step the CI quickstart leaves off until you're connected.

Providers are github and gitlab (add --host for GHES / self-hosted GitLab, or --issuer + --subject for any other OIDC provider). See cilock trust for the full flag set.

Where to next

  • Wire it into a pipeline end-to-end with the CI quickstart — now you can flip enable-archivista: true.
  • For the per-command details, see the CLI reference (login, use, whoami, doctor, trust, run, verify).
  • To author the policies your uploaded evidence is verified against, see the policy schema.

Reference generated from the product documentation. Match commands and support details to your installed release.