pinact: Pin GitHub Actions to Commit SHAs
pinact edits your workflow files so every uses: line references a full commit SHA with the tag kept as a comment, closing the mutable-tag supply-chain hole.
A uses: actions/checkout@v4 tag can be moved by the action owner to any commit. Pinning to a SHA freezes exactly what runs. pinact does the pinning and keeps it updated.
What it does
pinact resolves each uses: reference to the commit SHA behind its tag and rewrites the line as owner/repo@<sha> # vX.Y.Z. pinact run pins in place; pinact run --check verifies without editing; pinact update bumps pinned SHAs to the latest release.
Common usage
pinact run # pin all uses: to SHAs, in place
pinact run --check # exit nonzero if anything is unpinned
pinact run .github/workflows/ci.yml
pinact update # update pinned SHAs to latest tagsOptions
| Flag | What it does |
|---|---|
| run | Pin uses: references to full commit SHAs |
| run --check | Verify only; nonzero exit if unpinned actions remain |
| run --verify | Verify the SHA still matches the tag |
| update | Update pinned SHAs to the newest tag |
| --update / -u | Update existing pins while pinning |
| GITHUB_TOKEN (env) | Token used to resolve tags to SHAs and avoid rate limits |
In CI
Add pinact run --check as a required PR check so any newly added action must be SHA-pinned to merge. Schedule pinact update on a bot branch (Renovate or a cron workflow) so pins do not go stale.
Common errors in CI
pinact run --check fails with a diff listing lines like actions/checkout@v4 is not pinned. GET https://api.github.com/... 403 API rate limit exceeded means no GITHUB_TOKEN is set; export one. failed to parse points at malformed workflow YAML, so lint with actionlint first. command not found: pinact means the binary is not installed.