ci: upload SLSA provenance by release ID, not via the generator's auto-upload#225
Conversation
…o-upload The v0.15.4 release run failed the same way again, but with a worse side effect: the provenance job's upload-assets: true relies on softprops/action-gh-release, which finds the target release by tag. GitHub's "get release by tag" API does not return draft releases, so against our still-draft release it found nothing and created a *second*, non-draft release for the same tag instead of failing cleanly. That second release immediately and permanently marked the tag as immutable -- confirmed in production: deleting the bad release afterward did not free the tag back up, and the original (correct) draft, with all 23 assets, could never be published under that tag. Recovery required abandoning v0.15.4 and cutting v0.15.5. Fix: stop relying on the generator's built-in upload entirely. - provenance job: upload-assets is now false. The generator still produces the multiple.intoto.jsonl artifact, just doesn't try to attach it to a release. - publish job: downloads the provenance artifact, resolves the draft release by listing releases and filtering for the matching tag with draft == true (never by tag name), uploads the file directly via the GitHub REST API, then publishes. This never gives any tool a chance to "helpfully" create a duplicate release. - Tag name flows through env + jq --arg rather than string interpolation into a --jq filter, avoiding the same class of injection issue CodeRabbit flagged on the shell command earlier in this PR. - dev-docs/CI.md documents the failure mode and why the fix avoids it. Validated locally: goreleaser check / make test pass, and the jq selection logic was sanity-checked against a synthetic draft+published pair for the same tag. The actual draft lookup, upload, and publish sequence can only be verified by a real release run in GitHub Actions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 5 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Bomly Diff SummaryCompared Overview
Dependency ChangesSummary: 1 added, 0 changed, 0 removed. Added Dependencies
Vulnerabilities✅ No vulnerability changes. License Changes✅ No license changes. Project Posture✅ No project posture changes ( Policy FindingsSummary: 1 introduced, 0 persisted, 0 resolved. Introduced Findings
|
Summary
Follow-up to #223. The
v0.15.4release run failed the same way again (run 28432642577), but with a much worse side effect than the first failure.Root cause: the
provenancejob'supload-assets: truerelies onsoftprops/action-gh-release, which resolves its target release by tag. GitHub's "get release by tag" REST endpoint does not return draft releases — they aren't associated with a tag ref until published. Against our still-draft release (from #223's design), it found nothing, and instead of failing cleanly it created a second, non-draft release for the same tag, then failed to upload to that one too (immediately immutable on creation).The real damage: that second release's mere creation permanently marked the
v0.15.4tag as immutable at the platform level — confirmed in production. Deleting the bad release afterward did not free the tag back up; the original, correct draft (with all 23 real assets) could never be published under that tag. Recovery meant abandoningv0.15.4entirely and cuttingv0.15.5.Fix
Stop relying on the SLSA generator's built-in upload entirely — it has no way to target a draft release.
provenancejob:upload-assetsis nowfalse. The generator still produces themultiple.intoto.jsonlartifact (available viaactions/download-artifactandneeds.provenance.outputs.provenance-name), it just doesn't try to attach it to a release.publishjob: downloads that artifact, resolves the draft release by listing releases and filtering for the matching tag withdraft == true(client-side, never via the by-tag endpoint), uploads the file directly via the GitHub REST API (uploads.github.com, by release ID), then publishes. No tool ever gets a chance to "helpfully" create a duplicate release again.jqfilter viajq --arginstead of string-interpolation into the filter expression — same class of injection concern CodeRabbit flagged on the shell command in ci: keep release draft until provenance lands, fixing immutable-release upload failure #223, just in jq syntax this time, so fixed proactively rather than waiting for another round-trip.dev-docs/CI.mddocuments the failure mode and why the fix avoids it (including the "tag gets permanently poisoned" detail, since that's the genuinely sharp edge here).Recovery already done (outside this PR)
v0.15.4.v0.15.4is unrecoverable as a tag; the nextAuto Versionrun will need to cutv0.15.5.Verification
goreleaser check/make testpass locally (only the pre-existingbrewsdeprecation warning remains).jq --argdraft-selection logic against a synthetic JSON array containing both a draft and a published release for the same tag — correctly selects only the draft's ID.multiple.intoto.jsonl) against the realSHA256SUMSalready on the draft release — hashes matched exactly for all 20 subjects, confirming the artifact itself was always correct; only the upload mechanism was broken.What I could not test locally: the draft lookup → upload → publish sequence end-to-end, since it depends on GitHub Actions' artifact passing and the real REST API. Needs verification on the next real tagged release.
Test plan
goreleaser check/make testpass locallyjq --argdraft-selection logic sanity-checked against synthetic dual-release datav0.15.5):releasejob creates a draft,provenancejob produces the artifact without attempting upload,publishjob finds the draft by ID, uploads provenance, and publishes successfullynotify-landing-yank.ymlfires correctly off therelease: publishedevent🤖 Generated with Claude Code