Attest a published release that missed its attestation - #514
Conversation
v1.7.0 published its assets but the release run stopped at the size report before "Attest build provenance", so it shipped without GitHub attestations. mise requires them once an earlier version had them, and refuses the upgrade (#499), which also breaks hey upgrade on installs managed by mise. Re-running the release job would rebuild and re-sign, producing digests that are not what was published. The new "Attest a published release" workflow attests the release as it stands instead: it verifies checksums.txt against the release run's own cosign bundle (release.yml at the tag, the identity the installers pin), checks every checksum against the published asset's digest, and then attests checksums.txt the way release.yml does. mise does not pin the signer workflow, so an attestation signed by this workflow satisfies it.
Sensitive Change Detection (shadow mode)This PR modifies control-plane files:
|
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The documentation misidentifies the attestation subject, and the permission test does not enforce required read access.
Review effort: Balanced
Findings: 1
Open (2)
What changed in this PR
Adds a secure manual workflow for restoring missing provenance attestations on already-published releases.
Changes:
- Verifies signed checksums against published assets before attestation.
- Adds sensitive-change gating and static workflow tests.
- Documents the release recovery procedure.
[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or rungh pr ready --undo.
Click "Ready for review" or rungh pr readyto reengage.
| File | Description |
|---|---|
.github/workflows/attest-release.yml |
Adds the manual verification and attestation workflow. |
.github/workflows/sensitive-change-gate.yml |
Protects changes to the workflow. |
tests/e2e/attest_release_workflow.bats |
Tests workflow ordering, identity, pinning, and permissions. |
RELEASING.md |
Documents missing-attestation recovery. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Rejecting write permissions alone let contents: read be deleted, which would leave the release download and the digest check unable to read the release.
subject-checksums treats checksums.txt as an index of subject names and digests, so the attestations are for the assets it lists, not for the file. That is why the verification example names an archive.
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
The implementation appears sound, but production supply-chain attestations and release-environment permissions require final human validation.
Review effort: Balanced
Findings: None


v1.7.0 published its assets, but the release run stopped at the size report before Attest build provenance, so the release has no GitHub attestations. mise requires attestations for a tool once an earlier version had them, so it refuses v1.7.0. That also breaks
hey upgradeon installs that mise manages (#499).Re-running the release job would not fix this: goreleaser would rebuild and re-sign, and the new digests would not match what is published. This adds a manually dispatched Attest a published release workflow (
attest-release.yml, inputtag) that attests the release as it already exists:cosign verify-blobonchecksums.txtagainst the release run's ownchecksums.txt.bundle. It pinsrelease.yml@refs/tags/<tag>, the same identity the installers andhey upgradepin.checksums.txt(the file is the index of subjects, not a subject itself) with the same pinnedactions/attest-build-provenancethatrelease.ymluses.The job has
contents: read,id-token: writeandattestations: writepermissions, and runs in thereleaseenvironment. It is added to the sensitive-change gate and documented in RELEASING.md under "Missing attestation".tests/e2e/attest_release_workflow.batspins the signer identity, the order of the steps, the shared action pin, and that the job keepscontents: readwith no write permission.The attestation's signer will be
attest-release.ymlonmain, notrelease.ymlat the tag. mise's GitHub backend passes no expected signer workflow (src/backend/github.rs:None, // We don't know the expected workflow), so any attestation from this repo satisfies it.hey upgrade's own verification uses the cosign bundle, not attestations, and is unaffected.I ran the checks from steps 2 and 3 locally against v1.7.0: the bundle prints
Verified OK, all 27 checksums match published assets, and a checksum with a changed digest is caught. actionlint and zizmor report nothing.After merging:
Refs #499
Summary by cubic
Adds a manually dispatched workflow that attests an already-published release that missed its build-provenance attestation, restoring mise and
hey upgradesupport for v1.7.0.The workflow verifies
checksums.txtagainst the release run's own cosign bundle pinned torelease.yml@<tag>, checks every checksum against the published asset's digest, and then attests each asset listed inchecksums.txtwith the same pinnedactions/attest-build-provenanceasrelease.yml. Re-running the release job would rebuild and resign, changing digests, so this attestation matches the published assets instead. The attestation's signer is this workflow rather thanrelease.yml; mise doesn't pin the signer workflow, so it satisfies the requirement. Also adds the workflow to the sensitive-change gate and documents the procedure in RELEASING.md, noting the attestations cover the assets named in the checksum file, not the file itself.Written for commit 2aaeb47. Summary will update on new commits.