Skip to main content

Build Provenance / Attestations

SLSA build-provenance for binaries and archives, in subjects (OIDC) or emit (self-contained) mode

The attestation stage produces SLSA build-provenance for your binaries and archives. It runs after checksum (so subject digests reuse the sha256 the checksum stage already computed) and before sign.

Classification

Integrity — supply-chain metadata alongside release artifacts. Required: not a publisher; a no-op unless attestations.enabled is true.

Two modes

GitHub's actions/attest-build-provenance is OIDC-bound to the Actions run, so anodizer cannot mint a GitHub-trusted attestation itself. The mode: field selects how anodizer participates.

ModeWho attestsTrustOutput
subjects (default)actions/attest-build-provenance (OIDC)GitHub-trusteddist/attestation-subjects.json
emitanodizer + your sign keykeyed (weaker)attestation.intoto.jsonl (signed, uploaded)

Configuration

attestations:
  enabled: true
  mode: subjects          # or: emit ; default = subjects
  # artifacts: omitted → attest ALL release artifacts (see below)
FieldTypeDefaultDescription
enabledboolfalseEnable the stage. When false, no-op.
modesubjects | emitsubjectsParticipation mode (see below).
artifactslistall release artifactsWhich produced-artifact kinds to attest. The concrete subject set (filenames + sha256) is derived from the artifacts anodizer already produced — never hand-listed.
skipbool | templateSkip the stage (also --skip=attest).

What gets attested

When artifacts: is omitted, anodizer attests every release artifact — the full set that lands on the GitHub release, minus signatures/certificates (which sign other artifacts) and the attestation outputs themselves (no self-attestation). A .deb, SBOM, or installer you ship is attested by default rather than silently dropped.

To narrow the set, list one or more KINDS (not files):

KindCovers
archivepackaged archives (.tar.gz, .zip) + self-extracting archives
binaryraw uploadable binaries
checksumchecksum files (checksums.txt + split sidecars)
packageLinux packages (.deb / .rpm / .apk) + source RPMs
sourcesource archive tarball
sbomgenerated SBOM documents
installerWindows MSI/NSIS, macOS DMG, macOS PKG
# Example: attest only Linux packages and their checksums
attestations:
  enabled: true
  artifacts: [package, checksum]

If attestations is enabled but the selected kinds match no produced artifacts, the stage emits a warning (it does not silently produce nothing).

Mode subjects (default) — the OIDC path

anodizer writes a subjects manifest that anodizer-action feeds to actions/attest-build-provenance. anodizer does NOT attest itself in this mode; the Action's OIDC identity does. This is the path used by fd, biome, and gping.

dist/attestation-subjects.json is an array of subjects:

[
  {
    "name": "myapp-1.0.0-linux-amd64.tar.gz",
    "digest": { "sha256": "9f86d0818..." }
  },
  {
    "name": "myapp_1.0.0_checksums.txt",
    "digest": { "sha256": "2c26b46b6..." }
  }
]

How the manifest is consumed

The JSON manifest is INPUT to anodizer-action's own code, which iterates the entries and fans each out to the stock action via subject-name + subject-digest (one subject per { name, digest.sha256 } pair):

# anodizer-action reads dist/attestation-subjects.json and, per entry, calls:
- uses: actions/attest-build-provenance@v1
  with:
    subject-name: myapp-1.0.0-linux-amd64.tar.gz
    subject-digest: sha256:9f86d0818...

Do not point the stock action's subject-path: at attestation-subjects.json. subject-path expects the ACTUAL artifact files (the action hashes them itself); aiming it at the manifest would attest the JSON file's own hash, not your artifacts.

Consuming directly with the stock action

If you drive actions/attest-build-provenance yourself (without anodizer-action), the recommended path is subject-checksums pointed at the checksum file anodizer's checksum stage already writes:

# Recommended: subject-checksums reads the existing checksums.txt
# (sha256sum format `<hex>  <name>`); no extra file is generated.
- uses: actions/attest-build-provenance@v1
  with:
    subject-checksums: dist/checksums.txt

Alternatively, point subject-path at the real artifact globs (NOT the manifest) so the action hashes the files itself:

- uses: actions/attest-build-provenance@v1
  with:
    subject-path: "dist/*.tar.gz,dist/checksums.txt"

anodizer does not duplicate the sha256sum file: checksums.txt is in <hex> <name> format, which subject-checksums accepts directly. The JSON manifest is the primary deliverable for anodizer-action; reusing checksums.txt for the stock-action path is a zero-cost bonus.

Mode emit — self-contained (GoReleaser Pro parity)

For users who can't run the Action. anodizer generates an in-toto v1 statement carrying an SLSA provenance v1 predicate over the selected artifacts and writes it to dist/attestation.intoto.jsonl:

{
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [
    { "name": "myapp-1.0.0-linux-amd64.tar.gz", "digest": { "sha256": "9f86d0818..." } }
  ],
  "predicateType": "https://slsa.dev/provenance/v1",
  "predicate": {
    "buildDefinition": {
      "buildType": "https://anodizer.dev/release/v1",
      "externalParameters": { "tag": "v1.0.0", "version": "1.0.0" },
      "internalParameters": {},
      "resolvedDependencies": []
    },
    "runDetails": {
      "builder": { "id": "https://anodizer.dev" },
      "metadata": { "invocationId": "v1.0.0" }
    }
  }
}

The statement is registered as an uploadable artifact, so:

  1. The existing signs: stage signs it (cosign/gpg) — anodizer adds no new signing path; the statement rides the same signs: loop as every other artifact when artifacts: all.
  2. The release stage uploads attestation.intoto.jsonl (and its signature) as a release asset.

To sign the emit-mode statement, configure a sign block that covers all artifacts:

attestations:
  enabled: true
  mode: emit
signs:
  - artifacts: all      # signs the .intoto.jsonl alongside archives/checksums
    cmd: cosign

This mode is keyed (not OIDC) and carries weaker trust than the Action path.

Determinism

The in-toto statement omits the optional startedOn / finishedOn timestamps so two retries of the same tag produce byte-identical statement bytes — a re-uploaded asset never trips GitHub's already_exists size check.

Workspaces

In workspace per-crate mode, each published crate's output is written under a crate-prefixed name so they don't clobber:

dist/alpha.attestation-subjects.json
dist/beta.attestation-subjects.json
dist/alpha.attestation.intoto.jsonl    # emit mode
dist/beta.attestation.intoto.jsonl

Each manifest/statement covers only its own crate's artifacts.