fix: Reject asset publishing from recovery-target dispatch runs#1814
Conversation
|
Warning Review limit reached
Next review available in: 53 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 Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThe recovery publish workflow now rejects publishing when the resolved release commit differs from ChangesRecovery publish validation
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ 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 |
Attestation certificates bind SourceRepositoryDigest to the workflow run head (GITHUB_SHA), never to the checked-out build commit. A recovery-target dispatch run on a newer head therefore tags the historical release commit while attesting the new head, and the dispatcher's tag-to-certificate check can never pass. This broke uloop-project-runner-v3.0.0-beta.48 on 2026-07-15: every cold-cache download fails verification on all platforms. Enforce the invariant RELEASE_SHA == GITHUB_SHA whenever a run publishes attested assets: the publish job now fails closed with guidance to rerun the original failed run (whose head is the release commit). Recovery dispatch remains usable for build validation. The alternative of always tagging GITHUB_SHA on dispatch was rejected because it abandons the "runner tag == release-please merge commit" semantics; the guard keeps the semantics and only blocks the broken publish path. The runbook now documents the invariant and the beta.48 incident.
8bdd76b to
83cb6fa
Compare
ed38d7a
into
feature/windows-v3-verification-integration
Summary
Runner release
uloop-project-runner-v3.0.0-beta.48is permanently broken: its tag points at the historical release commit (2d8d1b94, the release-please merge commit), but its attestation certificates carrySourceRepositoryDigest = 2c73c6ac(the branch head at publish time). The dispatcher strictly verifies that the release tag's commit equals the certificate digest, so every cold-cache download of beta.48 fails attestation verification on all platforms. Any project pinned to beta.48 cannot bootstrap.Root cause
The
recovery-targetdispatch mode added in #1797 has a structural contradiction:native-cli-publish.ymlchecks out the resolver-derived historical release commit, builds it, and tags that commit.actions/attestproduces Fulcio certificates whoseSourceRepositoryDigestcomes from the OIDC token claims of the workflow run — it is always the run head (GITHUB_SHA), never the checked-out commit.On 2026-07-15 the beta.48 recovery was performed via a new recovery-target dispatch run (after the owner pre-created the tag and draft release to work around the HTTP 403 on historical commits). The dispatch run's head had advanced past the release commit, so the published assets carry a digest that can never match the tag.
Invariant
A run that attests published assets must satisfy
RELEASE_SHA == GITHUB_SHA. No guard enforced this anywhere — that missing guard is the actual defect this PR fixes.Changes
.github/workflows/native-cli-publish.yml: the publish job's "Verify release metadata" step now fails closed when a recovery-target dispatch resolvesrelease_sha != GITHUB_SHAwhilepublish == true, with an error that points to the correct procedure (rerun the original failed run whose head is the release commit). Recovery dispatch remains usable for build validation (publish == false).docs/release-recovery-runbook.md: documents the publishing invariant, explicitly forbids publishing through a new dispatch run on a later head, and records the beta.48 incident as background.scripts/test-native-cli-publish-workflow.sh: new testtest_recovery_dispatch_refuses_to_publish_from_a_different_headasserting the guard exists, is placed afterrelease_shais resolved, and fires beforeRELEASE_SHAis exported (TDD: written first and confirmed failing, then green after the fix).Design decision
The alternative — always tagging
GITHUB_SHAon dispatch runs — was rejected: it abandons the "runner tag == release-please merge commit" semantics. The guard preserves the semantics and only blocks the publish path that can never produce a verifiable release.dispatcher-publish.ymlis intentionally untouched: it has no recovery-target input and already requires its release target to equalGITHUB_SHA.Verification
sh scripts/test-native-cli-publish-workflow.shpasses (and the new assertions failed before the fix).sh -n.gh: recovery + publish + different head fails with the new message; recovery + release-only + different head still passes; recovery + identical head + publish still passes.Note
This PR only prevents future occurrences. Recovering beta.48 itself (rerunning the original failed run whose head is the release commit, or superseding it with a new release) is a separate operator action.