osec Action pins cover: pin every GitHub Action to a commit in two minutes

Why bother

• uses: owner/action@v4 isn't a version. It's a tag: a label the action's owner can point at different code any time.
• tj-actions/changed-files, 14 March 2025: every version tag was moved to one malicious commit. Workflows in more than 23,000 repositories followed. 218 public repos printed their secrets into public logs. Full story: tj-actions/changed-files 2025.
• Pinned workflows were fine. A full commit SHA can't be moved. They kept running the old, clean code.
• GitHub says the same: pinning to a full-length commit SHA is the only way to use an action as an immutable release.

What you need

• The uses: lines from your .github/workflows/*.yml files.
• Public actions on github.com. Your own repo can be private: you paste lines, not files.
• Two minutes. Free, no account.

Pin them in five steps

1. Open osec.one/tools/action-pins.
2. Paste the uses: lines, up to 30 at once. Indentation and - don't matter.
3. Click Get the commit SHAs.
4. Read the flag under each action (next section).
5. Click Copy lines, replace the old lines in your workflow, open a pull request.

osec Action pin lookup result: five uses: lines pinned to commit SHAs with version comments, flags that v4 and v46 are moving major-version tags and that codecov-action@main is a branch
Five real lines: every one pinned, one flagged as a branch.

Read the flags

• Moving major-version tag (grey, e.g. @v4): the owner moves it to each new release. The pin freezes today's commit; the comment says which release (# v4.4.0).
• Branch (amber, e.g. @main): every push changes what your workflow runs. The pin freezes today's commit. Read that commit before you trust it.
• Exact version tag (green, e.g. @v6.18.0): fine, but a tag is still a tag. Pin it anyway.
• Tag moved since an earlier lookup (red): an exact tag now points at a different commit than when someone last looked it up here. That's the tj-actions pattern. Stop, check the repo's releases and issues before you pin.
• No tag or branch called … (red): a typo, or the ref was deleted.
• Already pinned to a full commit SHA (green): nothing to do.

What goes back in your workflow

yaml
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c # v46.0.5
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1

• Keep the comment. It's how people (and Dependabot) know which release a SHA is.
• Pin your own reusable workflows too: uses: org/repo/.github/workflows/x.yml@<sha>.
• Local actions (uses: ./.github/actions/x) live in your repo already. Nothing to pin.

Keep pins current with Dependabot

1. Add .github/dependabot.yml (the result page has it ready to copy).
2. Dependabot opens a pull request when a pinned action releases. It updates the SHA and the comment.
3. Don't merge the same hour. A short wait skips most bad releases: Dependabot now waits three days by default (our note on the 2026 changes).

Keep pins up to date box on the Action pin lookup page with a dependabot.yml for the github-actions ecosystem, weekly
The dependabot.yml from the result page: github-actions ecosystem, weekly.

Make it stick

• Enforce it: org or repo Settings → Actions → General → allowed actions policy → Require actions to be pinned to a full-length commit SHA (GitHub, August 2025). Unpinned workflows then fail.
• Turn it on after pinning, or the next run of every unpinned workflow fails.
• Block a known-bad action in the same policy: prefix it with !.

Find the lines you missed

1. Paste the repo into the Workflow risk check.
2. It lists actions on a tag or branch for each workflow file and each composite action (action.yml).
3. Click Pin them with the Action pin lookup →. The lines arrive pre-filled.

Workflow risk check finding for ci.yml: 3 actions on a tag or branch, with a link Pin them with the Action pin lookup
One click from a Workflow risk check finding to the pinned lines.

• Publish an action yourself? The same check now reads composite action.yml files: actions inside them that aren't pinned, and ${{ inputs.* }} pasted into run: steps, each with the env: fix.

Workflow risk check finding in a composite action.yml: an action input pasted into run:, with an env: fix to copy
A composite action finding on a real repo, with the fix to paste.

Good to know

• Reads public data only: each action's tag list from github.com, the same as git ls-remote. Nothing from your repo.
• Up to 30 lines and 15 action repos per lookup.
• Moved-tag warnings only cover tags someone looked up here before.
• A pin means "the same code next time", not "safe code". Read new actions before you add them.
• Install scripts in the same pipeline? Pin those with Pinned download: steps here.

Paste your uses: lines, get commit SHAs with version comments and a flag on anything that moves. Free, no account.

Pin your actions

Sources

• GitHub Docs, security hardening for GitHub Actions (using third-party actions): https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions
• GitHub changelog, Actions policy now supports blocking and SHA pinning actions (15 August 2025): https://github.blog/changelog/2025-08-15-github-actions-policy-now-supports-blocking-and-sha-pinning-actions/
• CISA, supply chain compromise of tj-actions/changed-files (CVE-2025-30066) and reviewdog/action-setup (CVE-2025-30154): https://www.cisa.gov/news-events/alerts/2025/03/18/supply-chain-compromise-third-party-tj-actionschanged-files-cve-2025-30066-and-reviewdogaction-setup-cve-2025-30154
• GitHub Docs, Dependabot options (github-actions ecosystem, cooldown): https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference
• osec.one Action pin lookup, live result (18 October 2026): https://osec.one/tools/action-pins?id=ym7f4a6ANhnU