SHA Pin Generator for GitHub Actions
Paste your workflow. Get every uses: line rewritten to a full commit SHA — the only reference GitHub treats as immutable — along with a maintenance and security check on each action.
Tags are mutable: @v4 is resolved at run time, so whoever controls the repository can move it to a different commit. That is how tj-actions/changed-files was compromised in March 2025 — tags across many versions were repointed at a commit that dumped CI runner memory into build logs, exposing secrets in public repositories (CVE-2025-30066). Pinning would not have stopped the repository takeover, but it would have kept affected workflows on the commit they had already vetted.
Why pin GitHub Actions to a commit SHA
A GitHub Actions reference can point at a tag, a branch or a commit. Only the last one is fixed. Tags and branches are pointers the repository owner can move at any time, and GitHub resolves them freshly on every workflow run — so the code you reviewed in January is not necessarily the code that runs in June.
This matters because a third-party action runs inside your job, with access to your runner's memory, your workspace and any secrets you pass it. A compromised action is a compromised build. The March 2025 tj-actions/changed-files incident (CVE-2025-30066) is the reference case: an attacker with write access rewrote the action's tags to point at a malicious commit, and every workflow using a mutable tag picked it up automatically. Workflows pinned to a full commit SHA kept running the commit they had pinned.
Pinning is not a complete defence — it does not stop a malicious release you then upgrade to, and it does not fix an unmaintained action. It removes one specific, repeatedly exploited class of attack: the silent swap.
How to pin a GitHub Action
Replace the tag with the full 40-character commit SHA and keep the human-readable version in a trailing comment, so the line stays reviewable:
- uses: actions/checkout@v4 # mutable — avoid - uses: actions/checkout@11bd719... # v4.2.2 # immutable
Short SHAs are not enough — use the full 40 characters. To find the SHA yourself, open the action's release on GitHub and copy the commit it points at, or run git ls-remote https://github.com/owner/repo refs/tags/v4. The tool above does this for every action we track, and adds the version comment for you.
Two caveats worth knowing. First, a tag can point at an annotated tag object rather than a commit, so copy the commit SHA rather than the tag object's SHA. Second, actions published from a subdirectory (owner/repo/tools/lint@v1) are pinned the same way — the SHA belongs to the repository, not the subdirectory.
What about Dependabot and staying up to date
The usual objection to pinning is that you freeze on an old version and miss security fixes. That is a real risk, and the answer is Dependabot: it supports SHA-pinned actions natively, opening pull requests that bump the SHA and rewrite the trailing version comment. Enable it with a few lines:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weeklyThe combination is what makes pinning practical: the SHA stops anything changing under you, and Dependabot makes every change an explicit, reviewable pull request. If you pin without an update mechanism, you have traded one risk for another.
Immutable releases don't replace pinning
GitHub's immutable releases make a published release and its tag impossible to move or delete. Where an action has it enabled, the specific attack described above becomes much harder, and ActionRank records whether each action we track has it on.
It is not a full substitute. It has to be enabled on the action's repository, so most of the ecosystem is not covered yet. Floating major tags like v4, branch references and any tag that is not part of a release remain mutable. And GitHub's own hardening guidance still tells you to pin third-party actions to a full-length commit SHA. Treat immutable releases as defence in depth, not as a reason to stop pinning.
Pinning doesn't fix an abandoned action
A pinned SHA guarantees you keep running the same code. It says nothing about whether that code is still maintained. That is why this tool also reports maintenance status, archived repositories and known advisories for every action it recognises — pinning an abandoned action just freezes you on unmaintained code.
Frequently asked questions
Why should I pin GitHub Actions to a commit SHA?
Tags and branches are mutable references. `uses: owner/action@v4` is resolved when the workflow runs, so anyone who can write to that repository — including an attacker who has taken it over — can move the tag to a different commit and have your runner execute it. A full 40-character commit SHA is the only reference GitHub treats as immutable. In March 2025 the tj-actions/changed-files action was compromised exactly this way: tags were repointed at a commit that printed CI runner memory into build logs, exposing secrets in public repositories. It is tracked as CVE-2025-30066.
Does pinning to a SHA mean I stop getting updates?
No. Dependabot understands SHA-pinned actions: enable the github-actions ecosystem in .github/dependabot.yml and it opens pull requests that bump the SHA and rewrite the trailing version comment for you. Pinning moves updates from silent and automatic to reviewed and explicit — which is the point.
Do GitHub's immutable releases mean I no longer need to pin?
Not entirely. Immutable releases stop a published release and its tag from being moved or deleted, which removes much of the tag-rewrite risk where it is enabled. But it has to be enabled on the action's repository, branch references and tags that are not part of a release stay mutable, and GitHub's own hardening guidance still recommends pinning third-party actions to a full-length commit SHA.