A workflow that references an action by tag or branch runs whatever code that tag points to on the day of the run, with the job's secrets and token in reach. This guide covers one task: moving every action reference in an organisation from mutable tags to full-length commit SHAs, keeping those pins current with Dependabot, and then turning on the policy that fails any unpinned workflow. It also covers workflow dependency lockfiles, which GitHub now describes as the stronger option where they are available. It is written for platform engineers and organisation owners who already run GitHub Actions. For the wider credential picture see the GitHub and GitHub Actions Security Guide; this page goes deeper on pinning alone.
This is an independent guide from NHI Mgmt Group, based on GitHub's public documentation as of November 2026. It is not sponsored or reviewed by GitHub. Product features change, so confirm settings against GitHub's current documentation.
Key takeaways
- Pin to all 40 characters. GitHub's secure use reference recommends pinning to a full-length commit SHA because replacing the code behind it would need a SHA-1 collision for a valid Git object, while a tag "can be moved or deleted" by anyone who gains access to the action's repository.
- Keep a version comment on the same line. Dependabot updates a reference of the form
owner/action@<sha> # v4.2.1together with its comment, so pins stay readable and current. - Turn on the pinning policy last. The checkbox "Require actions to be pinned to a full-length commit SHA" fails every workflow that uses an unpinned action, including GitHub-authored and your own organisation's actions, so pin first, then enforce.
- Lockfiles go further than pins. Workflow dependency lockfiles (public preview, GitHub Enterprise Cloud) record transitive actions and the source repository's numeric identity; GitHub says SHA pinning in workflow files is no longer necessary for repositories that adopt one.
- Pinning moves the review burden. Dependabot does not raise vulnerability alerts for actions pinned to SHAs, so your update pull requests and their review become the control.
When you need this
Pinning is the right task when any of these is true: workflows reference third-party actions by @v4, @main or similar; jobs that use those actions hold cloud credentials, publishing tokens or a writable GITHUB_TOKEN; or an audit has asked you to show that the code your pipelines run is fixed and reviewed. It sits beside two other controls. The allowed actions policy decides which actions may run; pinning decides which version runs; token scoping decides what that code can reach. See How to Restrict GITHUB_TOKEN Permissions in Workflows for the third.
| Reference form | What runs | Can it change under you? | Use it for |
|---|---|---|---|
owner/action@main | Latest commit on the branch | Yes, on every push | Nothing in production workflows |
owner/action@v4 | Whatever the major tag points to | Yes, maintainers move it by design | Short-lived experiments only |
owner/[email protected] | The commit the tag points to | Yes, if the tag is force-pushed, unless it belongs to an immutable release | Better, still trust-based |
owner/action@<40-char SHA> # v4.2.1 | Exactly one commit | No | The default for all workflows |
| Lockfile entry | The locked commit, checked against the recorded owner and repository IDs | No, and transitive actions are covered | Repositories on Enterprise Cloud that can adopt the preview |
Immutable releases help maintainers: GitHub's guidance for action authors notes that a tag tied to a GitHub release cannot later be moved, whereas plain major and minor tags are moved by force-push as new versions ship. As a consumer you cannot tell from a uses: line which kind of tag you have, which is why the SHA is the safer default.
Before you start
- Access: write access to the repositories you will change, and organisation owner (or enterprise owner) rights to set the actions policy later.
- Tooling: the GitHub CLI (
gh) for resolving tags and, if you use lockfiles, thegithub/gh-actions-lockextension. - Inventory: a list of every
uses:line in.github/workflows/and in anyaction.ymloraction.yamlfile for composite actions you maintain. Code search foruses:scoped to your organisation and the path.github/workflowsgives a first cut; a clone-and-grep across repositories gives a complete one. - Classification: mark each reference as GitHub-authored, your own organisation, Marketplace third party, local (
./path), Docker image or reusable workflow. They are treated differently below. - Baseline: record the count of unpinned references per repository. It is your first metric and your proof of progress.
Step-by-step: bulk pinning
- Resolve each tag to a commit, from the action's own repository. GitHub warns that you should verify the SHA comes from the action's repository and not a fork. Resolve the tag you already use (for example
v4.2.1, notv4) to the commit it points to with the GitHub CLI orgit ls-remoteagainst the canonical repository. For an annotated tag,git ls-remoteprints two lines: the tag object's own hash and a second line ending in^{}, which is the object the tag points to. Pin to the^{}value, which is the commit, not the tag object hash. - Prefer the exact patch version. If a workflow uses
@v4, resolve the newestv4.x.yrelease the maintainers have published and pin to that, so the comment names a real release Dependabot can track. If the pinned commit has no associated tag, GitHub notes that Dependabot will update to the latest commit, which may differ from the latest release. - Rewrite the reference with a comment. Use the form
uses: owner/action@<full SHA> # v4.2.1on one line. Keep the comment on the same line; Dependabot updates it along with the SHA. - Do the same inside composite actions you own. A composite action's
action.ymlcan call other actions. Pinning the outer action does not pin what it calls unless its own file is pinned. This is the gap lockfiles close for the actions you consume but do not own. - Pin container images by digest. For
docker://steps and job or servicecontainer:images, reference the image by digest in the formimage@sha256:<digest>, as Docker's own pull syntax allows, so the same image runs every time. GitHub notes that Dependabot does not supportdocker://references for GitHub Actions, so track those through your container image update process. - Leave local actions and decide on reusable workflows. Local
./pathactions run from the same commit as the workflow and are not pinned. The pinning policy still allows reusable workflows to be referenced by tag. Our recommendation: pin the ones you call from other organisations to a SHA anyway, and govern your own through the allowed list. - Ship in small pull requests. One pull request per repository, or per workflow for large monorepos, with the diff limited to
uses:lines. CI on the pull request proves the pinned versions behave as the tags did.
Keep pins current with Dependabot
Add or extend .github/dependabot.yml with the github-actions ecosystem. GitHub's example uses package-ecosystem: "github-actions", directory: "/" and a weekly schedule.interval; the value / tells Dependabot to scan .github/workflows and the root action file. If you keep composite actions in other folders, use the directories key, which supports globbing (the singular directory key does not). Dependabot version updates then open pull requests that move both the SHA and its comment.
One trade-off matters. GitHub states that Dependabot only creates alerts for vulnerable actions that use semantic versioning and will not create alerts for actions pinned to SHA values. Once you pin, the weekly version update pull requests are how you pick up fixes, so they need an owner and a service level (our suggestion: review within five working days, sooner for security fixes announced by the action's maintainers).
Optional: adopt a workflow dependency lockfile
Where you run GitHub Enterprise Cloud and accept a public preview, GitHub's lockfile feature replaces per-line pinning. Install the extension with gh extension install github/gh-actions-lock, then run gh actions-lock from the repository root (or gh actions-lock .github/workflows/ci.yml to onboard one workflow). It writes .github/workflows/actions.lock, recording for each dependency the ref, the resolved commit, and the source repository's owner_id and repo_id. At run time GitHub uses those IDs to confirm the repository behind the name is still the one you locked, and transitive dependencies are covered.
- Review the diff: the tool also rewrites
uses:lines to the specific resolved version (for example@v3to@v3.1.0). - Check your allowlist: with a lockfile, the allowed actions policy checks locked commit SHAs, so tag-specific allowlist entries such as
actions/checkout@v6no longer match; useactions/checkout@*or a full SHA. - Protect the file: add
.github/workflows/actions.lockto CODEOWNERS with required code owner review, and rungh actions-lock --verifyon pull requests as a required check. - Treat identity changes as incidents in waiting: GitHub says a changed
owner_idorrepo_idmeans the repository behind the name is not the one originally locked. Investigate before merging. - Relock deliberately: if the tool reports that a tag now points to a different commit, read the upstream history first, then use
gh actions-lock --relock. Never hand-edit the lockfile.
Dependabot regenerates the matching lockfile entry when it bumps an action in an onboarded workflow, but it does not onboard new workflows.
Step-by-step: turn on the policy
- Pick the scope. The SHA pinning checkbox sits under each option of the allowed actions policy at enterprise, organisation and repository level (it is hidden where Actions is disabled). Start with one pilot organisation or repository.
- Set it. At repository level: Settings, Actions, General, Actions permissions, then tick "Require actions to be pinned to a full-length commit SHA" and Save. Organisation and enterprise settings carry the same option under their Actions policies.
- Tighten the allowed list at the same time. The option "Allow OWNER, and select non-OWNER, actions and reusable workflows" accepts patterns such as
octocat/*, octokit/*, up to 1,000 entries. Since August 2025 an entry prefixed with!blocks a specific action or version and is evaluated last, so*, !space-org/action@*allows everything except that action. - Watch the first runs. Any workflow that still uses an unpinned action fails. A failure in the pilot is the test working; fix the reference rather than relaxing the policy.
- Widen in rings. Pilot, then teams with the fewest third-party actions, then everyone. For lockfiles, GitHub's "Require lockfile" policy has an Evaluate mode on GitHub Enterprise, with results under Policy insights, before you set it to Active.
Verify it worked
- Search again: the inventory query from "Before you start" should return no
uses:lines without a 40-character hexadecimal ref, apart from local actions and the reusable workflow exceptions you accepted. - Test the guard: in the pilot repository, open a branch that changes one reference back to a tag. The run should fail with a pinning error; delete the branch.
- Check Dependabot: the repository's Dependabot page should list the github-actions ecosystem with a recent check, and the first update pull requests should change SHA and comment together.
- Check the audit trail: changes to Actions policies at organisation and enterprise level appear in the audit log. See the GitHub Audit Log Detection Guide.
Rollback and common errors
| Symptom | Likely cause | Fix |
|---|---|---|
| Workflow fails after the policy is enabled | An unpinned reference remains, often in a composite action or a rarely run workflow | Pin it; if production is blocked, untick the checkbox for that scope as the rollback, fix, then re-enable |
| Reference cannot be resolved | The tag object hash was copied instead of the commit, or the SHA came from a fork | Use the ^{} line from git ls-remote against the canonical repository |
| Dependabot updates the SHA but not the comment | The comment is on a separate line or in a different form | Put # vX.Y.Z on the same line as the reference |
| Dependabot moves to an untagged commit | The original pin had no associated tag | Re-pin to a tagged release commit |
| Allowlist stops matching after lockfile adoption | Tag-specific allowlist entries | Use owner/action@* or full SHAs in the allowlist |
| Lockfile verification fails on a pull request | A workflow changed without regenerating the lockfile | Run gh actions-lock and commit the result |
Rollback is a single setting: clearing the pinning checkbox (or setting the lockfile policy back to Evaluate) restores the previous behaviour immediately. The pinned references themselves need no rollback; they run the same code the tags pointed to when you pinned them.
Hardening afterwards
- Review update pull requests as code changes: a changed SHA means different code will run with your secrets. Read the action's release notes and diff for actions that run in privileged jobs.
- Scope what pinned code can reach: read-only token defaults and short-lived cloud credentials limit the damage if a pinned version is itself bad. See GitHub Actions OIDC Federation Hardening Guide.
- Review before allowing: a Marketplace verified creator badge means GitHub has verified the publisher's identity, which GitHub's secure use reference calls a useful signal; it is not a review of every version, so review the action itself before allowing it.
- Add the other CI/CD controls: see How to Find GitHub Workflows Exposed to pull_request_target Abuse and How to Audit CI/CD Secrets Scope.
Measure it
Our suggested metrics, reviewed monthly: unpinned third-party references per organisation (target zero); share of organisations with the pinning policy or lockfile policy active (target 100%); median age of open Dependabot github-actions pull requests (target under five working days); repositories with a github-actions Dependabot configuration (target 100% of those with workflows); and lockfile verification failures merged without regeneration (target zero).
Framework mapping
| Framework | Reference | How pinning supports it |
|---|---|---|
| NIST SP 800-53 Rev 5 | SI-7 Software, Firmware, and Information Integrity; SR-4 Provenance; CM-2 Baseline Configuration | Fixes and records the exact third-party code a build runs |
| NIST CSF 2.0 | ID.AM-02; PR.PS-02 | Inventory of actions in use; maintained and updated by risk |
| MITRE ATT&CK | T1195.001 Compromise Software Dependencies and Development Tools | Reduces exposure to a modified dependency or development tool |
| OWASP CI/CD Top 10 | CICD-SEC-3 Dependency Chain Abuse | See the OWASP CI/CD Top 10 CICD-SEC-3 Dependency Chain Abuse Guide |
Practitioner checklist
- Inventory every
uses:reference across workflows and composite actions, and record the unpinned count. - Resolve each tag to its commit in the action's own repository, never a fork.
- Pin to the full 40-character SHA with a same-line
# vX.Y.Zcomment. - Pin container images by digest and track them outside Dependabot's github-actions updates.
- Configure Dependabot for
github-actions, usingdirectoriesfor composite actions in subfolders. - Assign an owner and review target for Dependabot action pull requests.
- Enable "Require actions to be pinned to a full-length commit SHA" on a pilot, then widen in rings.
- Where eligible, pilot workflow dependency lockfiles with CODEOWNERS and
gh actions-lock --verifyas a required check.
Standards and references
- GitHub Docs: Secure use reference
- GitHub Docs: Managing GitHub Actions settings for a repository
- GitHub Changelog: Actions policy now supports blocking and SHA pinning actions
- GitHub Docs: Keeping your actions up to date with Dependabot
- GitHub Docs: Dependabot options reference
- GitHub Docs: Workflow dependency lockfiles
- GitHub Docs: Lock workflow dependencies
- GitHub Docs: Enforce workflow lockfiles
- GitHub Docs: Dependabot supported ecosystems and repositories
- GitHub Docs: About Dependabot alerts
- GitHub Docs: Using immutable releases and tags to manage your action's releases
- Git documentation: git ls-remote
- Docker Docs: docker image pull (pull by digest)
Related NHI Mgmt Group resources: GitHub and GitHub Actions Security Guide · How to Restrict GITHUB_TOKEN Permissions in Workflows · GitHub Organisation Security Checklist · CI/CD Pipeline Identity Security Guide