Skip to content

Repository files navigation

SLSA Attester

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.

Installing

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@latest

Every 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-signatures

The slsa-framework/actions repository does this for you in GitHub Actions.

Attesting GitHub Actions runs

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.

From inside the run

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@main

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

From outside the run

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

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

Where subjects come from

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.

Attesting builds

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/app

Flags 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/app

Resource descriptors (--resolved-dependency, --byproduct, --builder-dependency) take JSON, @file, or the shorthand name=…,uri=…,sha256=….

Attesting verifications

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.

Signing

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 statement

Keys may be PEM (PKCS#8, PKCS#1, SEC1) or OpenPGP. A SPIFFE identity signs with --signing-backend spiffe.

Verifying

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 3

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

Using the library

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.

Releases of this repository

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.

Contributing

The SLSA attester is released under the terms of the Apache 2.0 license. Patches and issues are welcome in the repository.

About

SLSA attester and attestation library

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages