Repository navigation
[COVAL-7697] Tag releases without running checked-out code under a write token - #137
Merged
Merged
Conversation
…ite token prepare checked out the CI-tested commit with a persisted contents: write token, ran release_version.py from it, then pushed the tag (CodeQL alert #2). The version is now read in a read-only job with no persisted credentials, and a separate contents: write job creates the annotated tag through the API without a checkout. The workflow token defaults to contents: read; only the tag job and the release call get write access.
callumreid
marked this pull request as ready for review
October 15, 2026 13:42
ben-coval
approved these changes
Oct 15, 2026
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
CodeQL alert #2 (
actions/untrusted-checkout/critical, open onmainsince 2026-10-01) flags thepreparejob inrelease-on-version-bump.yml. Triggered byworkflow_run, it:workflow_run.head_sha, keeping thecontents: writetoken in.git/config;scripts/release_version.pyfrom that checkout;In practice the commit is trusted:
prepareruns only after a successfulpushCI run onmain, or on a dispatch frommain. CodeQL can't see thatif:guard, and the job still runs repository code next to a write token it doesn't need for that step.Change
prepareis read-only. It checks out the commit withpersist-credentials: falseand acontents: readtoken, runsrelease_version.py, and outputs the commit SHA, tag andrelease_candidate. It no longer needsfetch-depth: 0, because tags are looked up through the API.A new
tagjob holds the write token and runs no repository code. It hascontents: writeand no checkout. It keeps the old tag step's decisions:POST /git/tagsandPOST /git/refs.Existing tags are found by exact ref, because
matching-refsis a prefix search, and resolved to their commit withGET /commits/refs/tags/<tag>.Least-privilege defaults. The workflow-level token drops from
contents: write+issues: writetocontents: read. Onlytagand the reusablereleasecall get write access, and the call passes on thecontents: write+issues: writethatrelease.yml's jobs ask for.Release tags stay annotated, with message
vX.Y.Zand taggergithub-actions[bot], the same as v0.9.1. As before, a tag created withGITHUB_TOKENdoes not trigger another workflow, sorelease.ymlis still called directly.Related
validateinrelease.yml) and alert #3 (update-homebrew) are fixed in [COVAL-7631] Open a Homebrew tap pull request instead of pushing to main #136. That PR already rewrites those steps, so fixing them there avoids a merge conflict. The two PRs touch different files and can merge in either order.actions/cache-poisoning/poisonable-stepon the build job) and the three Rustcleartext-loggingalerts.Validation
actionlint(with shellcheck) is clean.tagjob's script locally with a stubbedghin four cases, and each produced the same outputs as the old step:release=false;release=false, withrelease_refset to the tagged commit;release=true, rerunning that commit;release=truewithrelease_refset to the CI-tested SHA.v0.9.1and returns 0 forv0.9.10. A plainmatching-refs/tags/v0.9prefix search returns 2 refs.commits/refs/tags/v0.9.1peels the annotated tag to21eb3d8.POSTcalls that create a real tag. They run on the next version bump. If they fail, the run fails beforerelease.ymlstarts, and a manualRelease on version bumpdispatch frommainretries cleanly.No database queries change.
🤖 Generated with Claude Code