ci: pin github actions to commit shas - #1040
Conversation
Mutable tags are movable pointers: whoever controls an action, or anyone who compromises it, can re-point the tag and the next run executes unreviewed code with the job's token. The sensitive spot here is release.yml, where release-please and the publish jobs run with release credentials. Same pattern as the tj-actions/changed-files incident (CVE-2025-30066). Pinning to the full commit sha freezes what runs; the version each sha corresponds to stays as a comment. Every sha was resolved from the upstream repository and cross-checked against its release tag. No workflow logic changes.
Without a permissions block, the test jobs' GITHUB_TOKEN inherits the repository default scope. Both only check out and build (the website build reads its API key from a dedicated secret, the GitHub token is unused), so they get contents read. The deploy and release workflows already declare scoped permissions and are untouched.
|
Thanks for spending time on this! To be honest, I don't quite see the benefit of this – it's not like we're using actions from untrusted sources. If I didn't trust the GitHub or the googleapis teams to have their security and supply chains in order, we probably wouldn't even use those actions. |
Fair question, and I agree on the premise: googleapis and GitHub are trustworthy teams. Pinning is not about distrusting them though, it is about what a tag can do without their consent. A tag is mutable infrastructure. In the tj-actions incident the maintainers were not malicious and the project was widely trusted: an attacker got hold of a token and re-pointed every existing version tag to malicious code, so every repo that referenced a "trusted" tag ran it on their next build. Trust in the team does not survive a compromise of the team's account or release infra, and that is the exact case a sha pin neutralizes. The same class of thing has hit other reputable projects (reviewdog's actions, the codecov uploader). The spot that made me open this PR is On missing updates: that is what the version comments are for. With a If you want a smaller change: keep the |
Hi, drive-by CI hardening in two commits, one logical change each.
Commit 1 pins every action to its commit sha, versions kept as comments. A tag like
@v4is a movable pointer: whoever controls the action, or anyone who compromises it, can re-point it and your next run executes their code with the job's token. The sensitive spot here isrelease.yml, where release-please and the npm publish jobs run with your release credentials. Same pattern as the tj-actions/changed-files incident (CVE-2025-30066). Release-please itself is unaffected, it reads commits, not action refs. If you later want the pins bumped automatically, adding agithub-actionsecosystem to your dependabot config does it, one small block.Commit 2 adds
permissions: contents: readto the two test workflows that had none. The scope is exact, not guessed: both only check out and build, the website build reads its Maps API key from a dedicated secret and the GitHub token is unused. The deploy and release workflows already declare scoped permissions and are untouched.One heads up: if the org uses an Actions allowlist in settings, patterns written against tags (like
owner/action@v4) stop matching once refs are shas and workflows refuse to start. Entries need to beowner/action@*in that case.All shas were resolved from the upstream repos and cross checked against their release tags. No workflow logic changes.
Found with the Plumber CLI (https://github.com/getplumber/plumber), verified on main. I will also open a PR which adds it to CI so this does not quietly drift back, that one is a bonus, this PR stands on its own.