Tag-pinned GitHub Actions are a supply chain bomb waiting to go off. @v3 and even @v3.1.0 are mutable references that the action author can rewrite at any time, and the 2022 tj-actions/changed-files and 2024 reviewdog/action-* incidents both abused exactly that. This tutorial shows you how to convert every third-party action in your repo to a full 40-character commit SHA, keep them updated safely with Dependabot, and add a pre-merge check that rejects any unpinned action. Prerequisites: gh CLI 2.30+, write access to your repo, and 20–40 minutes depending on how many workflows you maintain.
Why is tag pinning unsafe?
Tags are branch pointers — they can move. A malicious maintainer or an account takeover can re-push v3 to a commit that steals your secrets. A full commit SHA is immutable content-addressed, so a compromised tag cannot poison your build unless the attacker finds a SHA-1 collision.
Only GitHub-owned actions under actions/* are relatively safe to tag-pin because they are covered by GitHub's own supply chain controls. Everything else — including 50k-star actions from well-known maintainers — should be SHA-pinned.
How do I find the right SHA?
Use gh api to resolve a tag to its commit SHA, or click the tag in the GitHub UI and copy the full SHA from the URL. Never trust a short SHA: always use the full 40 characters.
gh api repos/sigstore/cosign-installer/git/refs/tags/v3.5.0 \
--jq '.object.sha'
# 59acb6260d9c0ba8f4a2f9d9af48c2b6d2a2a1e0
If the tag resolves to an annotated tag object rather than a commit, dereference it with the ^{} suffix or use git rev-parse. The GitHub API returns object.type: "tag" in that case — you need one more hop to reach the commit.
How do I convert my workflows?
Replace each uses: line with owner/repo@<40-char-sha> and add a comment with the human-readable version. The comment is what Dependabot updates alongside the SHA.
# before
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
# after
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
The trailing comment is not cosmetic — Dependabot parses it to decide which SHA to upgrade to next. If you omit the comment, Dependabot leaves the SHA frozen forever. That is actually acceptable for some orgs; just be deliberate about the choice.
How do I configure Dependabot?
Add a github-actions ecosystem entry in .github/dependabot.yml that groups Action updates and runs weekly. Dependabot automatically preserves SHA pinning when it opens PRs.
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
groups:
actions:
patterns: ["*"]
open-pull-requests-limit: 5
Grouping all Action updates into one PR per week is the right default for small teams — otherwise you drown in 15 PRs the day a popular action releases. Review the PR, run the full CI suite, and merge.
How do I enforce pinning in CI?
Run the zgosalvez/github-actions-ensure-sha-pinned-actions action in a PR check, itself pinned to a SHA. It fails the build if any workflow file references a tag instead of a SHA.
name: Enforce SHA pinning
on: [pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- uses: zgosalvez/github-actions-ensure-sha-pinned-actions@fc87bb5b5a97953d987372e74478de634726b3e5
with:
allowlist: |
actions/
github/
The allowlist lets you exempt first-party GitHub actions. Add your own org prefix if you maintain internal shared actions — those live under your control so tag pinning is acceptable.
How do I audit historical workflow runs?
Use actionlint plus a one-liner grep to surface any remaining tag references, then use gh run list to see which workflows executed them recently. This gives you a risk-ordered cleanup backlog.
actionlint -color
grep -rEn "uses: [^@]+@v[0-9]" .github/workflows/
# .github/workflows/deploy.yml:14: uses: actions/setup-node@v4
# .github/workflows/test.yml:22: uses: codecov/codecov-action@v3
Fix the most-run workflows first — they have the highest blast radius. A daily cron workflow using a tag-pinned action is a much bigger risk than one that runs only on release.
How Safeguard Helps
Safeguard scans your .github/workflows/ directory as part of repository onboarding and flags every unpinned or tag-pinned third-party action as a supply chain finding. Griffin AI correlates the action with known compromised-package intelligence from the OpenSSF and npm advisories, so you know if a pinned SHA lives downstream of a recent incident. The platform generates an SBOM of your CI pipeline — actions, container images, and inline scripts — giving you a second chain of custody alongside your application SBOM. Policy gates can block a PR merge when a workflow adds a new unpinned action or when a pinned SHA is known-malicious, and violations route to Slack or Jira automatically. Keep your CI as trusted as your production code path.