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; reviewdog actions 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: write
codeql.yml — security-events: write, runs analysis actions against the base branch
mqtt-compat.yml / sticky-pr-comment — pull-requests: write (the github-script step 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-action is third-party — exactly the higher-risk category (tj-actions, codecov-bash were all third-party).
- Reproducibility. Floating major tags (
@v4 → latest v4.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 # v7
Dependabot (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
Surfaced during review of #1094.
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.ymlworkflow, but the exposure is repo-wide, so tracking it here rather than scoping the fix to one file.Why it matters
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.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.
codecov/codecov-actionis third-party — exactly the higher-risk category (tj-actions, codecov-bash were all third-party).@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)
.github/workflows/ci.ymlactions/checkout@v4,actions/setup-node@v4,codecov/codecov-action@v5.github/workflows/codeql.ymlactions/checkout@v4,github/codeql-action/init@v3,github/codeql-action/analyze@v3,actions/dependency-review-action@v4.github/workflows/labeler.ymlactions/labeler@v5.github/workflows/benchmark-compare-serial.ymlactions/checkout@v4,actions/setup-node@v4.github/workflows/mqtt-compat.ymlactions/checkout@v7,actions/setup-node@v6,actions/setup-python@v6,actions/upload-artifact@v7.github/actions/sticky-pr-comment/action.ymlactions/github-script@v9.github/dependabot.ymlalready enables thegithub-actionsecosystem — 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# vNcomment on each release.Proposed fix
Pin all action refs across
.github/to full commit SHAs with a trailing# vNcomment, in one consistent sweep:Dependabot (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-riskcodeql/codecov/labelerworkflows on bare tags — worse than uniform tags — so the fix should cover everything at once.References
tj-actions/changed-filestag-moving supply-chain attack)Surfaced during review of #1094.