Skip to content

fix: Reject asset publishing from recovery-target dispatch runs#1814

Merged
hatayama merged 1 commit into
feature/windows-v3-verification-integrationfrom
fix/guard-recovery-dispatch-publish
Jul 17, 2026
Merged

fix: Reject asset publishing from recovery-target dispatch runs#1814
hatayama merged 1 commit into
feature/windows-v3-verification-integrationfrom
fix/guard-recovery-dispatch-publish

Conversation

@hatayama

@hatayama hatayama commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Summary

Runner release uloop-project-runner-v3.0.0-beta.48 is permanently broken: its tag points at the historical release commit (2d8d1b94, the release-please merge commit), but its attestation certificates carry SourceRepositoryDigest = 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-target dispatch mode added in #1797 has a structural contradiction:

  • The recovery branch of native-cli-publish.yml checks out the resolver-derived historical release commit, builds it, and tags that commit.
  • However, actions/attest produces Fulcio certificates whose SourceRepositoryDigest comes 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 resolves release_sha != GITHUB_SHA while publish == 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 test test_recovery_dispatch_refuses_to_publish_from_a_different_head asserting the guard exists, is placed after release_sha is resolved, and fires before RELEASE_SHA is exported (TDD: written first and confirmed failing, then green after the fix).

Design decision

The alternative — always tagging GITHUB_SHA on 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.yml is intentionally untouched: it has no recovery-target input and already requires its release target to equal GITHUB_SHA.

Verification

  • sh scripts/test-native-cli-publish-workflow.sh passes (and the new assertions failed before the fix).
  • Workflow YAML parses (js-yaml); the extracted "Verify release metadata" script passes sh -n.
  • Behavioral simulation with a stubbed 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.

Review in cubic

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@hatayama, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 53 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ceb3873a-fcd8-41d8-bc21-d9a33c9c74ba

📥 Commits

Reviewing files that changed from the base of the PR and between 8bdd76b and 83cb6fa.

📒 Files selected for processing (3)
  • .github/workflows/native-cli-publish.yml
  • docs/release-recovery-runbook.md
  • scripts/test-native-cli-publish-workflow.sh
📝 Walkthrough

Walkthrough

The recovery publish workflow now rejects publishing when the resolved release commit differs from GITHUB_SHA. The runbook documents the invariant and rerun procedure, while shell tests validate the guard and its execution.

Changes

Recovery publish validation

Layer / File(s) Summary
Enforce recovery publish head invariant
.github/workflows/native-cli-publish.yml, docs/release-recovery-runbook.md
The publish job fails closed when release_sha differs from GITHUB_SHA; recovery documentation requires rerunning the original release-headed workflow.
Validate recovery rejection
scripts/test-native-cli-publish-workflow.sh
Tests verify the mismatch condition, failure messaging, assignment ordering, and test execution.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: blocking asset publishing from recovery-target dispatch runs.
Description check ✅ Passed The description matches the patch and explains the invariant, workflow change, docs update, and test coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/guard-recovery-dispatch-publish

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.
@hatayama
hatayama force-pushed the fix/guard-recovery-dispatch-publish branch from 8bdd76b to 83cb6fa Compare July 17, 2026 11:57
@hatayama
hatayama merged commit ed38d7a into feature/windows-v3-verification-integration Jul 17, 2026
5 checks passed
@hatayama
hatayama deleted the fix/guard-recovery-dispatch-publish branch July 17, 2026 12:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant