Pin GitHub Actions by Commit SHA
Replace mutable action tags with full commit SHAs, keep the version in a comment, and let Dependabot update pins without reopening a supply-chain hole.
Task
Prevent a compromised or retagged GitHub Action from running unreviewed code in CI with access to repository secrets.
When to use this
A workflow uses actions/checkout@v4. If the tag is moved or the action repository is compromised, the next run executes different code with the workflow's token and secrets.
Prerequisites
- Workflows stored in .github/workflows
- Permission to edit workflows and enable Dependabot
- GitHub CLI or API access to resolve tag commits
Steps
1. List every action reference that uses a tag or branch instead of a full 40-character commit SHA.
grep -RInE 'uses: [^#]*@(v|main|master)' .github/workflows2. Resolve the tag to a commit SHA and replace the reference, keeping the human-readable version in a comment.
gh api repos/actions/checkout/commits/v4.2.2 --jq .sha- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
Expected output: The workflow references a full commit SHA and the comment records the release tag.
3. Enable Dependabot for GitHub Actions so pinned SHAs receive reviewed update pull requests.
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
Expected output: Dependabot opens a pull request when a pinned action has a newer release.
4. Add a CI check that rejects new mutable references so the policy does not decay.
if grep -RInE 'uses: [^#]*@(v|main|master)' .github/workflows; then exit 1; fiExpected output: A pull request that adds a tag-based action fails the policy check.
Troubleshooting
Problem
The SHA does not resolve
Solution
Use the commit SHA, not an annotated tag object SHA. The commits API returns the commit for a tag name.
Problem
Dependabot rewrites the comment or pin format
Solution
Review the update like any dependency change; the SHA should remain the reference and the tag should remain a comment.
Limitations
- A SHA pin protects against tag movement, not against a malicious commit that was legitimately pinned. Review action updates and limit workflow permissions.
- Pinning every action increases maintenance; Dependabot or a renovate policy is required to avoid stale, vulnerable pins.
Tags are pointers, not versions
actions/checkout@v4 is convenient and mutable. The repository owner can move the tag, and an attacker who compromises the repository can publish new code under the same reference. Pinning a full commit SHA makes the workflow run exactly the code that was reviewed. The tag in a comment keeps the update readable for humans.
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
Use the commit SHA, not the annotated tag object SHA. The GitHub commits API resolves a tag name to the commit. If the reference is not a 40-character hexadecimal SHA, it is still mutable.
Keep pins current
A pinned action still needs security updates. Dependabot’s github-actions ecosystem opens pull requests that update the SHA and the version comment. Review those like any dependency: read the release notes, check the diff, and confirm the workflow still passes. A pin that is never updated trades tag-movement risk for unpatched vulnerability risk.
Enforce the policy
A one-time cleanup is not enough; new workflows will copy the old pattern. Add a lightweight check to CI that fails when a uses: line ends in a tag or branch. Allow a documented exception list only when a reference cannot be pinned, and review it regularly. Also reduce workflow permissions and avoid exposing secrets to pull requests from forks.