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.
| Mode | Who attests | Trust | Output |
|---|---|---|---|
subjects (default) | actions/attest-build-provenance (OIDC) | GitHub-trusted | dist/attestation-subjects.json |
emit | anodizer + your sign key | keyed (weaker) | attestation.intoto.jsonl (signed, uploaded) |
Configuration
attestations:
enabled: true
mode: subjects # or: emit ; default = subjects
# artifacts: omitted → attest ALL release artifacts (see below)| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable the stage. When false, no-op. |
mode | subjects | emit | subjects | Participation mode (see below). |
artifacts | list | all release artifacts | Which produced-artifact kinds to attest. The concrete subject set (filenames + sha256) is derived from the artifacts anodizer already produced — never hand-listed. |
skip | bool | template | — | Skip 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):
| Kind | Covers |
|---|---|
archive | packaged archives (.tar.gz, .zip) + self-extracting archives |
binary | raw uploadable binaries |
checksum | checksum files (checksums.txt + split sidecars) |
package | Linux packages (.deb / .rpm / .apk) + source RPMs |
source | source archive tarball |
sbom | generated SBOM documents |
installer | Windows 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:atattestation-subjects.json.subject-pathexpects 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:
- The existing
signs:stage signs it (cosign/gpg) — anodizer adds no new signing path; the statement rides the samesigns:loop as every other artifact whenartifacts: all. - The
releasestage uploadsattestation.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.