Repository navigation
ci(release): release only tags whose commit is on the default branch - #94
Merged
Merged
Conversation
A new provenance job runs .github/release/verify-tag-provenance.sh in a full clone before anything is built: it fetches the tag and the allowed release branch afresh in one fetch, requires the peeled tag to be GITHUB_SHA (and HEAD), and requires git merge-base --is-ancestor to exit 0 for an allowed branch (exit 1 is "no", any other status fails). A shallow clone, a missing ref or any git error fails closed. build and release need the job. The CI workflow runs shellcheck and the script's tests. Claude-Session: https://claude.ai/code/session_01RbGbwRHvCEhsKLZfbvbbRg
Resolve release.yml: keep the provenance needs and the pinned ubuntu-24.04 runners, and pin the provenance job's runner the same way. Claude-Session: https://claude.ai/code/session_01RbGbwRHvCEhsKLZfbvbbRg
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The release workflow runs on any pushed
v*tag and publishes whatever commit the tag names. Nothing checks that this commit is in the history of the default branch (main): av*tag pushed on a feature branch commit, or on a commit that is on no branch at all, would be built, attested and published (and, for a stable version, sent to the Homebrew tap). The tag ruleset blocks moving and deletingv*tags, but not creating one on an arbitrary commit; only the localhephaestus publishchecks the branch, and a tag can be pushed without it.Root Cause
The workflow trusts the tag push event.
GITHUB_SHAis whatever the tag points to; no job relates it to an allowed release branch.Solution
A new
provenancejob runs.github/release/verify-tag-provenance.shin a full clone (fetch-depth: 0,persist-credentials: false);buildandreleaseneed it, so nothing is built or published unless it passes. The script:GITHUB_REF=refs/tags/<tag>), a full lowercase SHA (GITHUB_SHA) and exact branch names (no patterns); the allowed list in this workflow ismainonly;git rev-parse --is-shallow-repositorymust printfalse) and unlessHEADisGITHUB_SHA;git fetch(one consistent ref advertisement) into a private namespace (refs/release-provenance/*), so neither the checkout's local refs nor a tag moved after the run started are trusted;^{commit}, annotated or lightweight) and requires it to beGITHUB_SHA;git merge-base --is-ancestor GITHUB_SHA <branch>to exit 0 for an allowed branch. Exit 1 means "not in this branch"; any other status (128, ...) fails the check and is never read as "no" or "yes".git branch --containsoutput is not parsed.Anything it cannot establish (failed fetch, missing branch or tag, git error) fails closed. To release from a maintenance branch later, add its exact name after
mainin therun:line.The publish step itself already refuses a tag that no longer points to the built commit (Hephaestus
releasere-reads the tag through the API), and the tag ruleset forbids moving av*tag, so a move after this job is caught there.Security Impact
Closes the gap where a
v*tag created on a commit outside the default branch's history is released. No new token, permission or secret: the job hascontents: readand fetches anonymously (the repository is public).Compatibility
Every release tag published by this workflow so far is in
main's history (checked for all tags withgit merge-base --is-ancestor); the older tags v0.0.1 to v3.2.0 (released before Hephaestus, from a linemaindoes not contain) are not, but they were published by earlier workflows and are never re-released by a new run. No release has been cut from arelease/*branch (v6.5.x is onmain), so none is allowed. Re-runs of runs started before this change use the workflow of their original commit and are unaffected. The release adds one short job (a full clone).Tests
.github/release/verify-tag-provenance_test.shbuilds local repositories (no network) and checks 22 cases: default branch lightweight/annotated/older tag (pass); detached commit, branch not allowed, maintenance branch not allowed (fail); allowed maintenance branch (pass); shallow clone (fail); SHA not the tag's commit, HEAD not the SHA, tag moved or deleted after the run started, stale local tag (fail); missing allowed branch, unreachable remote,merge-baseexiting 128,merge-basesaying no, not a repository (fail); non-tag ref, short SHA, branch pattern (fail); usage error (exit 2).hephaestus check --workflows(the release workflow conventions) pass.release_workflow_test.gonow pins the provenance job (build and release need it,fetch-depth: 0, the exact allowed list, no condition) with 5 new mutations, each caught; with the previousrelease.ymlthe test fails (RED).CI
The CI workflow runs shellcheck and the test script (step "Release tag provenance check"), on pull requests and in the release's
cijob.Migration
None. Releasing from a maintenance branch requires adding it to the allowed list in
release.ymlfirst (deliberate, reviewed change).Risks
mainfails until the branch is added: intended (fail closed).https://claude.ai/code/session_01RbGbwRHvCEhsKLZfbvbbRg