Pin GitHub Actions to commit SHAs
Reference every third-party action by its full 40-character commit SHA (with the version as a trailing comment), not a mutable `@v4` tag or `@main` branch that its maintainer, or an attacker, can re-point.
Do this
Third-party actions are pinned to an immutable 40-character commit SHA, with the human-readable version kept as a trailing comment so Dependabot and Renovate still track upgrades. A compromised upstream tag can no longer silently change the code that runs under your workflow's secrets and GITHUB_TOKEN.123
steps:
# Pin to the 40-char SHA of the release you use; keep the version as a
# comment. Run `pinact run` on your @v-pinned file to fill these in.
- uses: actions/checkout@<40-char-sha> # v4.2.2
- uses: pnpm/action-setup@<40-char-sha> # v4.1.0Avoid this
A re-pointed tag runs new code under your secrets and GITHUB_TOKEN on the next CI run.
steps:
- uses: actions/checkout@v4 # tag, mutable, can be re-pointed
- uses: some-org/some-action@main # branch, anyone with push can change itWhy it matters
A tag like @v4 is a moving pointer. If the action's maintainer account is compromised, the attacker re-points the tag at malicious code and your next CI run executes it with full access to your secrets. This is not hypothetical, the tj-actions/changed-files compromise (March 2025, CVE-2025-30066) worked exactly this way. Pinning to a SHA makes the code immutable; a branch ref like @main is the worst case and should be pinned or vendored.
When to use
Use it when
Every third-party action reference. First-party actions/* are lower-risk but still worth pinning for a consistent policy.
Be careful when
Pinning has no real downside beyond upgrade friction, which Dependabot/Renovate handle via the version comment. For an action that only ships a main branch, vendor it (fork and pin your own SHA) or push upstream to cut releases.
Verify on your repo
Hand this prompt to your coding agent (Claude Code, Cursor, and the like) to audit and fix this practice in your own repo.
Inspect this repo's .github/workflows for third-party actions referenced by a mutable ref: `uses:` lines ending in `@v1` / `@v4` / `@main` / `@master` rather than a full 40-character commit SHA. For each, pin it to the immutable 40-char SHA of the release currently in use and keep the human-readable version as a trailing comment (`@<sha> # v4.2.2`) so Dependabot and Renovate still track upgrades; running `pinact run` performs this rewrite for you. Show me the diff and open a PR rather than applying it blindly.Prefer to check by hand?
Scan for unpinned references: look for
uses:lines ending in@v1/@v2/@main/@masterrather than a 40-char hex SHA.Convert them in one shot with `pinact` (
pinact run), which rewrites@vrefs to SHAs and preserves the version as a trailing comment.For stronger enforcement, run `zizmor` in CI: its
unpinned-usesaudit catches tag-pinned actions andimpostor-commitcatches SHAs that don't exist in the action's history.
More best practices for GitHub Actions
Where to go next in the CI best-practices catalog.
All CI best practicesFAQ
Do I need to pin first-party actions like actions/checkout?
Won't pinning to a SHA break my automated updates?
No, keep the version as a trailing comment (@<sha> # v4.2.2). Dependabot and Renovate read that comment and still open upgrade PRs, so you get immutability and updates.
Sources
1GitHub · security hardening (pin actions to a full-length SHA) (opens in new tab)
Last updated 2026-07-06
Pin every action to a commit you control.
One line to install. Faster runs on day one, and agents that open reviewable PRs to keep your pipeline following practices like this one.