ci: pin every action to an immutable SHA; stop persisting checkout credentials - #82
Merged
Conversation
…edentials A Greptile review on the membrane rollout of this same policy (antra-tess/membrane#45) flagged the github-release job for running actions/checkout at a mutable major-version ref: retargeting that tag would change the code running with permission to create and edit releases. This repo shipped that publish.yml in an earlier rollout of the policy, so the finding lands here identically — and it is live on main rather than sitting in a PR. Fixed across every workflow, not just the flagged line: 10 actions pinned to immutable SHAs across changelog.yml, ci.yml and publish.yml, version tags kept as trailing comments so the pins stay readable and Dependabot can still bump them. The review named the least privileged of the write-capable jobs. Two things it did not: - The npm publish job holds `id-token: write` for OIDC trusted publishing. A swapped action there can reach a live publish credential — a worse outcome than edited release notes. - checkout defaults to persist-credentials: true, writing the job token into .git/config where every later step in the job can read it. Nothing here pushes over git — publish uses OIDC, the release job uses gh with GH_TOKEN — so all 4 checkouts now set it false. No behavior change intended: each pinned SHA is the current head of the tag it replaces. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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
A Greptile review on the membrane rollout of this same contribution policy
(antra-tess/membrane#45)
flagged the
github-releasejob for runningactions/checkoutat a mutablemajor-version ref. That job holds
contents: write; retargeting the tag wouldchange the code running with permission to create and edit releases.
This repo shipped that
publish.ymlin an earlier rollout, so the findingapplies here identically — with the difference that here it is live on
main, not sitting in a PR. And now that the analysis is written up in apublic review thread, the reasoning is available to anyone who reads it, which
is reason enough to close it rather than file it.
The review named the least privileged of the write-capable jobs. Two things it
did not:
id-token: writefor OIDC trusted publishing,and ran the same mutable ref. A swapped action there can reach a live
publish credential — a considerably worse outcome than edited release notes.
actions/checkoutdefaults topersist-credentials: true, writing thejob token into
.git/config, where every later step in that job can readit. In a
contents: writeorid-token: writejob that is a durableambient credential sitting behind whatever the remaining steps do.
Changes
across
changelog.yml,ci.ymlandpublish.yml. Not just the flaggedline: a half-pinned repo invites the same finding next time, and the sharper
job was the unflagged one.
persist-credentials: falseon all checkouts. Nothing here pushes overgit — publish authenticates via OIDC, the release job via
ghwithGH_TOKEN— so no step needs the credential left behind.@<sha> # v4.4.0) so the pins stayreadable and Dependabot can still bump them.
Each pinned SHA is the current head of the tag it replaces, so no behavior
change is intended.
Tests
All workflow files parse as YAML, and a sweep for
uses:refs not matching@<40-hex>returns nothing — no action is left on a mutable ref.The pins were resolved through the GitHub API by dereferencing each tag to its
commit, not copied from another repo.
Not verified
CI runs. The publish and release jobs are tag-gated and cannot run until
the next release; the pin is a like-for-like ref swap, but it stays
unexercised on real release infrastructure until then. This is the same
exposure the policy rollout PRs already carried.
SHAs beyond confirming each SHA is that tag's current head — if a tag had
already been retargeted before today, this pins the current state rather
than an audited one.
Companion PRs
The same fix landed on the three open policy PRs, which carry the same
publish.yml:Safe to merge in any order — no shared files, and each repo's workflows are
independent.
Worth flagging from that batch: chronicle was the worst of the set. Its build
matrix ran
dtolnay/rust-toolchain@stable— a branch ref, not a version tag,so not merely mutable but expected to move — and that job cross-compiles the
.nodebinaries whichupload-artifacthands to the OIDC publish job. That isa supply-chain path straight into a signed npm artifact.
CHANGELOG.mdupdated under## Unreleased— or this change isinternal-only / test-only / docs-only (apply the
no-changeloglabel).CI-config only, with no consumer-visible behavior change, which the policy
explicitly excludes from needing an entry. The
changelogcheck will pass onits own since no
src/file is touched.🤖 Generated with Claude Code