Attest the release archives with build provenance and document how to verify a download - #68
Merged
Merged
Conversation
checksums.txt alone proves nothing: whoever can replace an archive on the Release can replace the checksum file beside it. The attestation is signed by Sigstore and stored on GitHub rather than as a Release asset, so a download can be checked against the workflow and commit that built it.
The checksum name was pinned for an attestation step that this branch adds, so the comment that pointed forward to it now points at the file next door.
178inaba
marked this pull request as ready for review
August 30, 2026 19:25
The permissions comment still answered why the block held only a read scope, which stopped being the question when the writes were added, and the attest step named an input whose own name says the same thing.
Merged
9 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
checksums.txton a Release proves nothing on its own. Whoever can replace an archive there can replace the checksum file sitting beside it, so the two fall together. A build provenance attestation does not: it is signed by Sigstore and stored on GitHub rather than as a Release asset, which lets anyone check that a downloaded archive came from this repository's release workflow, built from a given commit.This is the second and last stage of #63. The pipeline being attested — tagpr → GoReleaser, writing
dist/checksums.txtunder a fixed name — landed with #64, which pinned that name specifically so this step could attest through it.What
.github/workflows/release.yml— anactions/attest-build-provenance@v4step after GoReleaser, under the sameif: steps.tagpr.outputs.tag != ''gate, withsubject-checksums: dist/checksums.txt. One attestation covers all six archives; the checksum file's lines are the subjects..github/workflows/release.yml—id-token: write,attestations: writeandartifact-metadata: writeadded to thepermissionsblock, which is whatactions/attestdocuments as required. The comment above it is rewritten: it said the workflow's own token needs no more than the checkout, and this step makes that untrue. The App token from Release rdsh with tagpr and GoReleaser: prebuilt binaries, a Homebrew cask, and --version #64 is untouched — the attestation uses the default${{ github.token }}and wants nothing from the App.README.md— a### Verify a downloadsubsection at the end of## Installwith the one command and one sentence on what a pass proves..goreleaser.yaml— one word of tense. Itschecksum.name_templatecomment said the provenance was "being added next"; this PR is that change, and the comment travels to the sibling CLIs on the next copy, so it is corrected here rather than left to go stale.skills/rdsh/SKILL.mdis unchanged: verification is a one-time human step and no rdsh CLI surface moves, soCLAUDE.md's three-way documentation sync is not triggered.Deploy notes — a patch release is needed after this merges
v1.0.0is already published (2026-08-30) and was built before this change, so it carries no attestation and never will. That makes the conditional in #63's Manual release steps fire:Until a
v1.0.1is cut (the default patch bump — notagpr:label needed), there is no attested artifact and acceptance criterion 1 cannot be checked. The README's new subsection is worded to stay true in the meantime: it says what a pass proves, without asserting that the archives currently on the Releases page carry an attestation.Two decisions worth a look
The wrapper vs
actions/attestdirectly. The Issue asks forattest-build-provenance, so that is what this uses. Worth knowing that its v4 README now says "new implementations should useactions/attestinstead" — the wrapper is a composite overactions/attest@v4.2.2that forwards every input, and provenance isactions/attest's default mode, so the attestation produced is byte-for-byte the same decision either way. Raising it rather than swapping it silently; happy to switch if you would rather track the upstream recommendation.subject-checksumsrather thansubject-path: dist/*.tar.gz,dist/*.zip. The subject list then follows whatever GoReleaser actually built, so a later change to the target matrix needs no edit in the workflow. It also reuses digests GoReleaser already computed instead of re-hashing the six archives on the runner. Per the Issue's Deferred to implementer judgment,checksums.txtis not attested as a subject of itself — its lines are the subjects, andgh attestation verify <archive>resolves by the archive's own digest.How this was verified
The attestation itself cannot be produced off a real release, so what is checked locally is the shape of the change:
yq '.permissions'andyq '.jobs.release.steps[-1]'— the file parses and both edits landed as intended.mainskips it" and for this PR not breakingmain: a non-release push runs checkout → app token → tagpr and skips the rest, and widening the workflow token's scopes cannot fail a run.go test -race ./...and golangci-lint both pass — no Go code changes, so this is a formality; CI's lint job covers Go and.goreleaser.yamlonly.Left for after merge, on the
v1.0.1release:gh attestation verify <any of the six archives> --repo 178inaba/rdshsucceeds on a fresh download and fails on a modified copy, and the Release page still carries six archives pluschecksums.txtwith no attestation file among them.Closes #65
Part of #63