Store attestations in Archivista
Archivista is the searchable evidence store CI/lock integrates with by default. This guide covers how to configure CI/lock to push signed attestations to it, and how to retrieve them later.
When to use Archivista
Workflow artifacts (GitHub Actions / GitLab job artifacts) are fine for the first few weeks of adoption. Move to Archivista when you need:
- Cross-team verification: a release-gate workflow that fetches evidence about an artifact built last quarter, without rerunning CI.
- Searchable history: query by subject digest, predicate type, identity, or time window.
- Long retention: outlive your CI platform's artifact lifetime (typically 30–90 days).
The TestifySec Platform evidence store (default)
The TestifySec Platform runs Archivista at <platform-url>/archivista, so https://platform.testifysec.com/archivista for the hosted platform. cilock run derives that address and uploads to it by default whenever it has a platform identity that may upload. Each identity uploads with its own credential, never with its signing token:
| Identity | Uploads by default? | Credential |
|---|---|---|
| Enrolled agent | yes; the run fails if the platform issues no upload token | the agent's own upload token |
cilock login session | yes | the session |
GitHub Actions job (id-token: write, repository connected to your tenant) | yes | a job token for audience <platform-url>/archivista, which CI/lock mints and refreshes itself |
| GitLab.com job | only if the job declares an ID token with audience <platform-url>/archivista (for example CILOCK_ARCHIVISTA_ID_TOKEN) | that token |
| Buildkite or CircleCI job | no; it signs only, unless the worker has cilock login | the session |
The three audiences stay separate: Fulcio signing uses sigstore, cilock login uses <platform-url>/login, and upload uses <platform-url>/archivista.
In an authorized GitHub Actions job, the whole recipe is:
cilock run --step build -- go build ./cmd/myappIt signs keyless as the workflow, timestamps against the platform TSA, and uploads. If the platform refuses the upload, the run fails with upload to <platform> rejected ... the attestation was signed but not stored, and keeps the signed envelope on disk. To keep the evidence only as a file, pass --enable-archivista=false; an explicit value always wins. For a self-hosted platform, add --platform-url with your platform's URL.
Self-hosted Archivista
CI/lock also uploads to a standalone Archivista you run yourself:
| Model | When to pick it |
|---|---|
| Static API key | Self-hosted Archivista with a long-lived service account; quick setup. |
| OIDC (audience-bound) | CI workflows with a short-lived OIDC token; no static credentials in the pipeline. |
API key (raw CLI)
--offline keeps the TestifySec Platform's TSA and upload out of this run, so the evidence goes only where you point it.
cilock run --step build \
--offline \
--enable-archivista \
--archivista-server "$ARCHIVISTA_URL" \
--archivista-headers "Authorization: $ARCHIVISTA_API_KEY" \
--signer-file-key-path ./signing.key \
--outfile attestation.json \
-- go build ./cmd/myappThe --archivista-headers flag accepts arbitrary HTTP headers in Header: value form, which is how the API key gets forwarded.
The GitHub Action
When using aflock-ai/cilock-action, Archivista upload uses OIDC by default, no static API key in the workflow:
- name: build
uses: aflock-ai/cilock-action@d39bb9b17e392520f9e7eef17cf355c027a5c283 # v1.0.4
with:
step: build
command: go build ./cmd/myapp
attestations: environment git github sbom
platform-url: ${{ env.PLATFORM_URL }} # archivista URL derived from this
# enable-archivista: true is the defaultFor the raw CLI in a GitHub Actions step, no token step is needed: use the recipe above.
Useful flags
| Flag | Purpose |
|---|---|
--enable-archivista | Enable the Archivista sink. Turned on by default when CI/lock has a platform identity that may upload (table above); --enable-archivista=false always wins. |
--archivista-server <url> | Archivista server URL. Derived from --platform-url if omitted. |
--archivista-headers <h> | Add an HTTP header (e.g. Authorization: <token>). Repeatable. |
--archivista-oidc | Use OIDC auth instead of headers. On by default in GitHub Actions. |
--archivista-audience <aud> | Audience claim for the OIDC token. For the platform it is derived as <platform-url>/archivista; for another server it is that server's URL. |
All verified from rookery/cilock/internal/options/run.go.
Verifying the upload
Each successful upload returns a GitOID that uniquely identifies the stored attestation. The cilock-action exposes it as a step output:
- name: build
id: build
uses: aflock-ai/cilock-action@d39bb9b17e392520f9e7eef17cf355c027a5c283 # v1.0.4
with:
step: build
command: go build ./cmd/myapp
platform-url: ${{ env.PLATFORM_URL }}
- name: Print evidence GitOID
run: echo "Stored at ${{ steps.build.outputs.git_oid }}"For raw CLI: the Stored in archivista as sha256:... line appears in CI/lock's stderr at upload time.
Querying stored evidence
Archivista exposes a GraphQL API, an HTTP API, and a CLI (archivistactl).
Option A: archivistactl (upstream CLI)
The fastest way for ad-hoc queries. Source: in-toto/archivista/cmd/Readme.md.
# Store
$ archivistactl store build.attestation.json
build.attestation.json stored with gitoid 4462a729...
# Search by subject digest (algorithm:value)
$ archivistactl search sha256:423da4cff198bbffbe3220ed9510d32ba96698e4b1f654552521d1f541abb6dc
Gitoid: 4462a729...
Collection name: build
Attestations: https://witness.dev/attestations/git/v0.1, .../environment/v0.1, ...
# Retrieve subjects for a stored attestation
$ archivistactl retrieve subjects 4462a729...
# Retrieve the full DSSE envelope
$ archivistactl retrieve envelope 4462a729...archivistactl ships separately from CI/lock and is the canonical reader-side tool.
Option B: GraphQL (programmatic)
The schema's top-level fields for evidence queries are dsses(where: ...) and subjects(where: ...). Both are GraphQL connections: each call returns one page of edges and a pageInfo. A digest lookup that reads every match asks for pageInfo and repeats the query with after set to endCursor while hasNextPage is true, which is how the Go client's searches page (attestation/archivista/client.go):
query($algo: String!, $digest: String!, $after: Cursor) {
dsses(
first: 100,
after: $after,
where: {
hasStatementWith: {
hasSubjectsWith: {
hasSubjectDigestsWith: { value: $digest, algorithm: $algo }
}
}
}
) {
edges {
node {
gitoidSha256
statement {
attestationCollections {
name
attestations { type }
}
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}Variables: {"algo": "sha256", "digest": "1a2b3c...", "after": null} for the first page, then "after" set to the previous page's endCursor. The same hasSubjectDigestsWith predicate works under subjects(where: ...) if you want subjects without the surrounding DSSE.
Option C: HTTP endpoints (direct curl)
For tooling that doesn't want to depend on GraphQL:
# Upload (POST)
curl -X POST "$ARCHIVISTA_URL/v1/upload" \
-H "Content-Type: application/json" \
--data-binary "@attestation.json"
# → {"gitoid":"72d83847..."}
# Download (GET)
curl "$ARCHIVISTA_URL/v1/download/<gitoid>"
# → the raw DSSE envelope JSON
# Ad-hoc GraphQL POST
curl -X POST "$ARCHIVISTA_URL/v1/query" \
-H "Content-Type: application/json" \
-d '{"query":"{ subjects(first: 100) { edges { node { name subjectDigests { algorithm value } } } pageInfo { hasNextPage endCursor } } }"}'
# → one page; while hasNextPage is true, repeat with subjects(first: 100, after: "<endCursor>")For the full schema, self-hosting setup, and the legacy ER diagram, see in-toto/archivista and the upstream cmd/Readme.md.
Troubleshooting evidence upload
| Symptom | Likely cause |
|---|---|
upload to <platform> rejected ... the attestation was signed but not stored | The platform refused the credential: an expired or wrong-audience token, a revoked or unenrolled identity, or a repository the platform has not been told to trust. The signed envelope is kept on disk. |
archivista upload failed: 403 | API key doesn't have write permission for that self-hosted Archivista instance. |
| No upload and no error | The run had no identity that uploads: a Buildkite or CircleCI job without cilock login, a GitLab job with no ID token for <platform-url>/archivista, --offline (or an empty --platform-url), or an explicit --enable-archivista=false. |
| Successful run but no GitOID printed | Check stderr, successful uploads log Stored in archivista as <gitoid>. |
Reference generated from the product documentation. Match commands and support details to your installed release.