slsa-attester generates signed SLSA attestations as
in-toto statements built on the official SLSA predicate protos:
- Build provenance, in the v1 and v0.2 predicates.
- Verification summary attestations (VSAs), v1.
- Provenance for GitHub Actions runs, with a watcher that observes a
run from a job of its own, waits for the other jobs, and attests the
artifacts they produced. This is the successor to the attestation half of
slsa-github-generator.
It is a command line tool and a Go library. What it produces verifies with
the SLSA Verifier: the two
tools share the algorithm:digest syntax for subjects, so a digest written
at attest time reads back verbatim at verify time.
Download a binary from the releases for Linux (amd64, arm64), macOS (arm64) or Windows (amd64), or build from source:
go install github.com/slsa-framework/attester@latestEvery release ships its own provenance, attestations.intoto.jsonl, generated by
the attester itself (see Releases of this repository);
releases up to v0.1.0 published it as attestations.jsonl.
To verify a download before trusting it:
slsa-verifier build attestations.intoto.jsonl slsa-attester-v0.1.0-linux-amd64 \
--param expected_source:github.com/slsa-framework/attester \
--param 'trusted_builders:[https://github.com/slsa-framework/attester/.github/workflows/release.yaml@refs/tags/v0.1.0]' \
--signer 'sigstore::https://token.actions.githubusercontent.com::https://github.com/slsa-framework/actions/.github/workflows/attest_actions.yml@refs/heads/main' \
--require-signaturesThe slsa-framework/actions
repository does this for you in GitHub Actions.
slsa-attester watch observes a workflow run, waits for its jobs to finish
and attests the artifacts they produced, as provenance of the
watcher build type
(https://slsa.dev/buildtypes/watcher/v1). The provenance names the
workflow that ran as the builder, records the ref, event, repository and
inputs the run was triggered with, and the commit it built.
The easiest way is the reusable workflow in slsa-framework/actions, added
as one more job to the workflow being attested:
jobs:
build:
runs-on: ubuntu-latest
steps:
# ... build and upload your artifacts ...
provenance:
permissions:
id-token: write # sigstore keyless signing
actions: read # read the run's jobs and artifacts
contents: read
uses: slsa-framework/actions/.github/workflows/attest_actions.yml@mainThe job waits for every other job of the run, downloads their workflow
artifacts, hashes them and signs the provenance. Because the signing
happens inside the reusable workflow, the sigstore certificate identifies
slsa-framework/actions, a builder identity independent of the workflow
being attested, the way slsa-github-generator worked. The attestation is
uploaded as a workflow artifact (slsa-attestations by default).
The workflow's inputs choose what is attested:
| Input | Meaning |
|---|---|
watch-jobs |
Only these jobs are waited for (default: every other job) |
collect-artifacts, expand-artifacts |
Attest the run's workflow artifacts, unpacking archives to one subject per file (both default on) |
artifacts-filter |
Globs on artifact and asset names; only matches are attested |
release |
Also attest the assets of this release tag |
sboms |
Collector sources of SBOMs whose top-level elements are attested |
subjects, checksums, dependencies |
Extra subjects as algorithm:digest, a sha256sum listing, and extra resolved dependencies |
version, verify, build-from-source |
Which attester runs, and whether its release is verified against its provenance first |
The attester must have a job of its own. Every step that runs earlier in a job can tamper with the attester, and every step in the job shares the OIDC identity the attestation is signed with. The watcher inspects its own job and refuses to run beside any other step. A hostile step that already ran could defeat that check too, which is why verifiers pin the signing identity rather than trust the check.
Any run can be watched by its spec, with a GITHUB_TOKEN in the
environment that can read it. The watcher waits for the whole run and
attests when it completes:
slsa-attester watch github://slsa-framework/attester/123456789 --release v0.1.0 -o provenance.jsonOutside the attested run the ref is resolved through the API, telling tags from branches; the workflow's inputs can only be recorded as the defaults the workflow declares, since the platform does not expose the values a run was given after the fact.
| Source | Flag | Digest computed by |
|---|---|---|
| Workflow artifacts of the run | --collect-artifacts (default) |
the attester, which downloads and hashes them |
| Release assets of a tag | --release <tag> |
the platform, which reports the digest it computed at upload; assets without one are downloaded and hashed |
| Top-level elements of an SBOM | --sbom <source> |
asserted by the SBOM; elements without hashes are rejected |
Checksum files (sha256sum output) |
--checksums <file> |
asserted by the caller |
| Explicit digests | -s algorithm:digest |
asserted by the caller |
SBOMs are located with the
carabiner collector, so a
source can be a directory (fs:sboms/), a release (release:owner/repo@v1.0.0)
or anything else a collector driver reaches, and are read with protobom,
so SPDX and CycloneDX documents work, bare or wrapped in an attestation.
slsa-attester build writes build provenance over subjects given as files,
which are hashed, or as digests:
slsa-attester build --builder-id=https://builder.example/ci \
--build-type=https://builder.example/buildtypes/container/v1 \
--invocation-id=run-42 -o provenance.json bin/appFlags use the latest (v1) vocabulary. Selecting the older predicate with
--predicate-version v0.2 maps them onto the older field names
(--invocation-id becomes metadata.buildInvocationId, resolved
dependencies become materials), and a flag that exists in only one version
is refused when targeting the other.
A whole predicate can be passed as a base with --predicate, as JSON or
@file. It is parsed strictly against the official SLSA proto of the
selected version, and the content flags are merged onto it:
slsa-attester build --predicate @predicate.json --finished-on 2026-10-05T12:00:00Z bin/appResource descriptors (--resolved-dependency, --byproduct,
--builder-dependency) take JSON, @file, or the shorthand
name=…,uri=…,sha256=….
slsa-attester vsa writes a verification summary attestation, the record
a verifier leaves of what it verified and to which level:
slsa-attester vsa --verifier-id=https://verifier.example \
--resource-uri=pkg:oci/app@sha256:9f86d0… \
--verification-result=PASSED --verified-level=SLSA_BUILD_LEVEL_3 \
--policy-uri=https://policies.example/release.json \
-s sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08--input-attestation records the attestations the verdict was based on,
and --dependency-level the levels of the resource's dependencies.
Every command signs by default, with sigstore: ambient credentials are
used where they exist (GitHub Actions, GitLab CI, GCP), with the browser
flow as the fallback. The result is a sigstore bundle, recorded in Rekor
and timestamped. --sigstore-instance github signs against GitHub's
sigstore instance instead of the public one.
With a key the result is a DSSE envelope; without signing, a bare statement:
slsa-attester build --signing-key key.pem ... # DSSE envelope; the passphrase comes from $SIGNING_KEY_PASSPHRASE
slsa-attester build --sign=false ... # unsigned statementKeys may be PEM (PKCS#8, PKCS#1, SEC1) or OpenPGP. A SPIFFE identity signs
with --signing-backend spiffe.
Attestations verify with the SLSA Verifier. The provenance's builder.id
is a claim; the verifier binds it to the signing identity. For provenance
generated through the reusable workflow, the certificate proves which
workflow's run the attester observed, so the builder is proven rather than
claimed:
slsa-verifier build attestations.intoto.jsonl your-artifact \
--param expected_source:github.com/your-org/your-repo \
--param 'trusted_builders:[https://github.com/your-org/your-repo/.github/workflows/release.yml@refs/tags/v1.0.0]' \
--signer 'sigstore::https://token.actions.githubusercontent.com::https://github.com/slsa-framework/actions/.github/workflows/attest_actions.yml@refs/heads/main' \
--require-signatures --level 3The release tag is held by the @ref on the trusted builder. The
verify/build action in slsa-framework/actions runs the same check in a
workflow.
The attest package exposes the same functionality through one canonical
option set; the per-version mapping and the compatibility checks happen
inside:
import "github.com/slsa-framework/attester/attest"
writer := &attest.Writer{}
err := writer.Attest(attest.SlsaProvenanceV1, []string{"bin/app"},
attest.WithBuilderID("https://builder.example/ci"),
attest.WithInvocationID("run-42"),
attest.WithWriter(out),
)Attest takes the format (SlsaProvenanceV1, SlsaProvenanceV02,
VsaV1) and the subject files; AttestSlsaProvenanceV1,
AttestSlsaProvenanceV02 and AttestVSAV1 are the same with the format
fixed. Subjects known only by digest come in with WithSubjects, a base
predicate with WithPredicate, and a Signer with WithSigner; without
one the statement is written unsigned.
Each release is attested by the attester itself: the release workflow has
one job that builds and publishes with goreleaser, and a second, dedicated
job that runs the reusable workflow to watch the first and attest the
release assets. The provenance is signed with the slsa-framework/actions
identity, so the command under Installing verifies it.
The SLSA attester is released under the terms of the Apache 2.0 license. Patches and issues are welcome in the repository.