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).
Two auth models
CI/lock supports two ways to authenticate to Archivista:
| 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. |
The wire-level details below are taken from Cole's test-staging-cilock.yaml reference.
Pattern 1: API key (raw CLI)
cilock run --step build \
--enable-archivista \
--archivista-server "$ARCHIVISTA_URL" \
--archivista-headers "Authorization: $ARCHIVISTA_API_KEY" \
--signer-file-key-path ./signing.key \
--outfile attestation.json \
-- go build ./cmd/myapp
The --archivista-headers flag accepts arbitrary HTTP headers in Header: value form, which is how the API key gets forwarded.
For TestifySec-hosted Archivista, the URL pattern follows <platform-url>/archivista (so https://web.platform.testifysec.com/archivista).
Pattern 2: OIDC (cilock-action default)
When using aflock-ai/cilock-action, Archivista upload uses OIDC by default, no static API key in the workflow. The action requests a fresh OIDC token whose audience matches the Archivista server URL:
- name: build
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 default
For the raw CLI inside a GitHub Actions step, fetch the OIDC token explicitly:
- name: get oidc token
id: oidc
uses: actions/github-script@v7
with:
script: return await core.getIDToken("sigstore")
result-encoding: string
- name: build
run: |
cilock run --step build \
--enable-archivista \
--archivista-server "$PLATFORM_URL/archivista" \
--archivista-oidc \
--archivista-audience "$PLATFORM_URL/archivista" \
--signer-fulcio-token "${{ steps.oidc.outputs.result }}" \
...
-- go build ./cmd/myapp
OIDC tokens are short-lived. Fetch a fresh token before each step: the dropbox-clone reference does this with a separate actions/github-script@v7 step in front of every cilock run.
Useful flags
| Flag | Purpose |
|---|---|
--enable-archivista | Enable the Archivista sink. |
--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. |
--archivista-audience <aud> | Audience claim for the OIDC token (typically the Archivista URL itself). |
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
with:
step: build
command: go build ./cmd/myapp
- 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: ...). The Go client's SearchQuery constant (used internally for digest-based lookups) is the reference shape:
query($algo: String!, $digest: String!) {
dsses(
where: {
hasStatementWith: {
hasSubjectsWith: {
hasSubjectDigestsWith: { value: $digest, algorithm: $algo }
}
}
}
) {
edges {
node {
gitoidSha256
statement {
attestationCollections {
name
attestations { type }
}
}
}
}
}
}
Variables: {"algo": "sha256", "digest": "1a2b3c..."}. 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 { edges { node { name subjectDigests { algorithm value } } } } }"}'
For the full schema, self-hosting setup, and the legacy ER diagram, see in-toto/archivista and the upstream cmd/Readme.md.
Troubleshooting
| Symptom | Likely cause |
|---|---|
archivista upload failed: 401 | OIDC token expired between fetch and use, or audience mismatch. Refresh the token before each step and confirm --archivista-audience matches the Archivista URL. |
archivista upload failed: 403 | API key doesn't have write permission for that Archivista instance. |
| Upload silently skipped | --enable-archivista not passed, or the platform-url default is set to a non-Archivista platform. |
| Successful run but no GitOID printed | Check stderr, successful uploads log Stored in archivista as <gitoid>. |