Most workflow steps look like this:
- uses: actions/checkout@v4
v4 is a tag, and a tag is just a name someone attached to a commit — the action's maintainer (or anyone who compromises their account) can move it to point at a different commit at any time. Pinning to a commit SHA instead means the reference cannot be silently changed: a SHA is the commit.
Use git ls-remote against the action's repository. No clone is needed, and this makes only the same kind of request your own git fetch already makes:
git ls-remote https://github.com/actions/checkout refs/tags/v4
That prints a line like:
8f152de45cc393bb48ce5d89d36b731f54556e65 refs/tags/v4
The first column is the commit SHA. If the tag has no matching commit under refs/tags/, try refs/heads/<name> instead — some actions reference a branch (@main) rather than a version tag.
Keep the original tag as a trailing comment, so a human reading the workflow still knows which version it is without decoding the hash:
- uses: actions/checkout@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4
That line now resolves to exactly one commit, forever. Upgrading later is a deliberate, reviewable diff: look up the SHA for a newer tag and replace both the hash and the comment.
A workflow can have several jobs, each with several steps, each `uses:`-ing a different action — and a single unpinned reference is enough to be the weak link. Paste your workflow YAML into Pinned Actions Check to find every mutable reference across every workflow file at once, each with its own ready-to-run git ls-remote command, or run the CLI in your own repository:
python3 check.py --fail-on-risk
That exits with a nonzero status if any third-party action is still referenced by a tag or branch instead of a commit SHA — wire it into your own CI to block a newly-added unpinned action before it merges.
In March 2025, the widely used tj-actions/changed-files action had its version tags repointed at a malicious commit that dumped CI secrets into workflow logs. Any workflow referencing it by tag (@v35, and similar) ran that malicious code the next time it triggered. A workflow that had pinned to a commit SHA was never affected by the retag, because its reference could not be moved.