Repository navigation
security: pin GitHub Actions to commit SHAs instead of mutable tags (repo-wide) #1099
Description
Summary
All GitHub Actions across .github/ are referenced by mutable version tags (@v4, @v9, …) rather than pinned to immutable commit SHAs. Tags are movable refs: the action's owner — or anyone who compromises the action repo — can re-point a tag to different code, which then runs in our CI on the next workflow run with no PR, no diff, and no notification. SHA pins (@<40-char-sha> # vN) are immutable: the only way our run changes is a reviewable commit that bumps the SHA.
This was surfaced while reviewing the new mqtt-compat.yml workflow, but the exposure is repo-wide, so tracking it here rather than scoping the fix to one file.
Why it matters
- Supply-chain substitution. A compromised action with a moved tag executes immediately in every downstream workflow. Real precedent:
tj-actions/changed-files(CVE-2025-30066, March 2025) had its version tags retroactively moved to secret-exfiltrating code, exposing ~23k repos;reviewdogactions were hit the same way. SHA-pinned consumers were unaffected. - Blast radius is amplified by token scope. Some of our workflows run with write permissions:
labeler.yml—pull-requests: writecodeql.yml—security-events: write, runs analysis actions against the base branchmqtt-compat.yml/sticky-pr-comment—pull-requests: write(thegithub-scriptstep holds the token while it runs)
A compromised action there is attacker-controlled code holding a write token, not just wrong results.
- Third-party > first-party risk.
codecov/codecov-actionis third-party — exactly the higher-risk category (tj-actions, codecov-bash were all third-party). - Reproducibility. Floating major tags (
@v4→ latestv4.x.y) drift under us: the same workflow file can behave differently over time with no change on our side.
Current state — every ref is a bare tag (nothing SHA-pinned)
| File | Action refs |
|---|---|
.github/workflows/ci.yml |
actions/checkout@v4, actions/setup-node@v4, codecov/codecov-action@v5 |
.github/workflows/codeql.yml |
actions/checkout@v4, github/codeql-action/init@v3, github/codeql-action/analyze@v3, actions/dependency-review-action@v4 |
.github/workflows/labeler.yml |
actions/labeler@v5 |
.github/workflows/benchmark-compare-serial.yml |
actions/checkout@v4, actions/setup-node@v4 |
.github/workflows/mqtt-compat.yml |
actions/checkout@v7, actions/setup-node@v6, actions/setup-python@v6, actions/upload-artifact@v7 |
.github/actions/sticky-pr-comment/action.yml |
actions/github-script@v9 |
.github/dependabot.yml already enables the github-actions ecosystem — but Dependabot only updates the pinning style already in use. On bare tags it bumps the tag; it never converts tags to SHAs and does nothing about a moved/floating tag. The immutability benefit only kicks in once refs are SHA-pinned, after which Dependabot maintains the SHA and the # vN comment on each release.
Proposed fix
Pin all action refs across .github/ to full commit SHAs with a trailing # vN comment, in one consistent sweep:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7Dependabot (already configured) then keeps both the SHA and the comment current, with every update arriving as a reviewable PR instead of a silent tag move.
Doing this piecemeal (only mqtt-compat.yml) would leave the higher-risk codeql / codecov / labeler workflows on bare tags — worse than uniform tags — so the fix should cover everything at once.
References
- GitHub: Security hardening for GitHub Actions — using third-party actions
- OpenSSF Scorecard: Pinned-Dependencies check
- CVE-2025-30066 (
tj-actions/changed-filestag-moving supply-chain attack)
Surfaced during review of #1094.
Activity