From 5111a3dd1756dbe8369e8f075e88b2b9f7213886 Mon Sep 17 00:00:00 2001 From: Mike Odnis Date: Sat, 3 Oct 2026 18:07:49 -0400 Subject: [PATCH] fix(release): attest and attach the SBOM before the immutable Release exists Releases here are immutable, so publishing one freezes its assets. The publish job created the Release with SHA256SUMS, then uploaded the SBOM (HTTP 422: Cannot upload assets to an immutable release), and the attestation step behind it never ran. v0.4.3 through v0.4.6 carry SHA256SUMS only and have no provenance attestation, although the workflow told users to verify one. Generate the SBOM and attest SHA256SUMS first, then create the Release with every asset in the one call that publishes it. A re-run against an existing Release now leaves it alone instead of trying to upload to it, and still attests. --- .github/workflows/release.yml | 69 ++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 059951f..b2f53d4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -293,6 +293,39 @@ jobs: | sort -k2 > SHA256SUMS cat SHA256SUMS + # ORDER MATTERS: everything that belongs on the Release, and the + # provenance for it, is produced BEFORE the Release exists. Releases in + # this repository are immutable — publishing one freezes its assets — so + # the old order (create with SHA256SUMS, then upload the SBOM, then + # attest) failed with "HTTP 422: Cannot upload assets to an immutable + # release" at the SBOM upload, and the attestation step behind it never + # ran. v0.4.3 through v0.4.6 shipped with SHA256SUMS only and no + # attestation, while the comment below told users to verify one. + + # SPDX JSON SBOM of the released commit. Written to a file and attached + # at creation rather than by the action itself: its own release upload + # only works on `release` events, and this workflow runs on push. + - name: Generate SBOM (SPDX JSON) + uses: anchore/sbom-action@3ad7283483fc7af8ff2b4ea19663c2d5ca935e26 # v0.24.2 + with: + path: . + format: spdx-json + output-file: sbom.spdx.json + upload-artifact: false + upload-release-assets: false + + # Provenance for what users actually run: every installer listed in + # SHA256SUMS, by digest. Verify one with + # gh attestation verify install.sh --repo resq-software/dev + # Attestations are stored against the repository, not the Release, so + # this step does not depend on it — and running it first means a + # workflow_dispatch re-run still attests a version whose Release + # already exists. + - name: Attest build provenance + uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 + with: + subject-checksums: SHA256SUMS + - name: Create GitHub Release env: GH_TOKEN: ${{ github.token }} @@ -300,10 +333,11 @@ jobs: set -euo pipefail # Idempotent alongside the tag step: a re-run must not fail because - # the release already exists. + # the release already exists. An immutable Release cannot be changed, + # so there is nothing to refresh; say what it carries and stop. if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then - echo "release $TAG already exists; refreshing SHA256SUMS only" - gh release upload "$TAG" SHA256SUMS --repo "$GITHUB_REPOSITORY" --clobber + assets="$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json assets --jq '[.assets[].name] | join(", ")')" + echo "release $TAG already exists and is immutable; leaving it as is (assets: ${assets:-none})" exit 0 fi @@ -335,37 +369,12 @@ jobs: cat SHA256SUMS echo '```' } > NOTES.md - gh release create "$TAG" SHA256SUMS \ + # Every asset in the one call that publishes: after it, none can be added. + gh release create "$TAG" SHA256SUMS sbom.spdx.json \ --repo "$GITHUB_REPOSITORY" \ --title "$TAG" \ --notes-file NOTES.md - # SPDX JSON SBOM of the released commit. Written to a file and uploaded - # below rather than by the action itself: its own release upload only - # works on `release` events, and this workflow runs on push. - - name: Generate SBOM (SPDX JSON) - uses: anchore/sbom-action@3ad7283483fc7af8ff2b4ea19663c2d5ca935e26 # v0.24.2 - with: - path: . - format: spdx-json - output-file: sbom.spdx.json - upload-artifact: false - upload-release-assets: false - - - name: Attach the SBOM to the Release - env: - GH_TOKEN: ${{ github.token }} - # --clobber keeps a re-run idempotent, like the SHA256SUMS refresh above. - run: gh release upload "$TAG" sbom.spdx.json --repo "$GITHUB_REPOSITORY" --clobber - - # Provenance for what users actually run: every installer listed in - # SHA256SUMS, by digest. Verify one with - # gh attestation verify install.sh --repo resq-software/dev - - name: Attest build provenance - uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 - with: - subject-checksums: SHA256SUMS - bump: name: propose pin bump needs: verify