Pin every GitHub Action to a SHA. One command.

2026-04-27 - pin-actions

Pinned actions are step one. Step two is the rest of the workflow.

The Cut Your CI Bill cookbook covers permissions, concurrency, timeouts, cache, and matrix strategy in 30 paste-ready patterns. $19, one-time, MIT-licensed templates.

Get the cookbook

Tags are mutable. Branches are mutable. The author of an action you use can rewrite v4 tomorrow morning to point at code that exfiltrates your GITHUB_TOKEN. The first you'd hear about it is when an attacker pushes commits to your default branch using the privileges your CI handed out the last time the workflow ran.

This is not theoretical. The tj-actions/changed-files compromise in early 2025 hit thousands of repos this exact way. Affected workflows pinned to a tag. The patched workflows pinned to a SHA.

GitHub's own security hardening guide says, plainly: pin third-party actions to a full-length commit SHA.

Nobody does this by hand

A 40-character SHA is not a thing humans want to type into YAML. So almost everyone uses tags. And the few people who pin to SHAs do it for one or two actions in one or two repos and then give up.

pin-actions is a free MIT CLI that walks every .github/workflows/*.yml, finds every uses: owner/repo@ref line, resolves the ref against the GitHub API, and rewrites the line in place to use the full SHA - keeping the original ref as a comment so the file is still readable.

Before:

- uses: actions/checkout@v4

After:

- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11  # v4

One command

npx pin-actions

That's the whole flow. It walks your workflows, calls the GitHub API to resolve each ref, rewrites the file, and prints what it changed.

In CI, use --check to fail the job if anything is unpinned:

npx pin-actions --check

That's a one-line, zero-config supply-chain CI gate.

What it leaves alone

Rate limits

Unauthenticated GitHub API requests are limited to 60 per hour. Most workflows have under ten unique uses: lines, so the unauthenticated limit is fine for one-off runs. For CI, pass a token:

GITHUB_TOKEN=ghp_xxx npx pin-actions --check

In a GitHub Actions workflow, ${{ github.token }} is the token to pass.

The companion tool

ci-doctor flags unpinned actions as a warning and points at pin-actions. The two-line workflow hardening pass is:

npx ci-doctor --fix     # adds permissions, concurrency, timeouts
npx pin-actions         # pins every action to a SHA

Both finish in under two seconds on a normal repo. Both are MIT.

Try it

npx pin-actions

Source on GitHub.