GitHub Actions Workflow Lockfiles: Pin Every Action, Including the Ones You Can't See
GitHub now records the exact commit of every direct and transitive action in .github/workflows/actions.lock. Here is how to adopt it, what it fixes that SHA pinning never did, and where it still leaks.
Key takeaways
- GitHub Actions workflow lockfiles, in public preview since October 14, 2026, record the exact commit and source repository of every direct and transitive action in .github/workflows/actions.lock.
- Generate one with the gh actions-lock extension, verify it in CI, and let Dependabot update it.
- It replaces SHA pinning but does not yet lock container images or cross-repo reusable workflows.
On this page
Workflow lockfiles stop actions from changing underneath you. gh actions-lock writes .github/workflows/actions.lock, recording the commit and source repository of every action you use, including transitive ones. GitHub Actions then runs the locked commits even if a tag moves. It entered public preview on October 14, 2026; adopt it now on your highest-risk repos.
The problem SHA pinning never solved
uses: actions/setup-node@v4 is a pointer, not a version. Tags and branches can move after you reviewed a workflow, so tomorrow's run can execute different code. That's how the March 2025 tj-actions/changed-files compromise worked: the attacker repointed existing version tags to a malicious commit, and every workflow referencing those tags ran it on the next push.
The standard advice was to pin full commit SHAs. That helps, with three gaps:
- Transitive actions. Your pinned composite action can itself call
some-org/helper@v2. You pinned the outer layer; the inner one still floats. - Repository identity. A SHA says which commit, not which repository. If an action's owner is renamed or deleted and the name is re-registered, the reference can resolve somewhere you never reviewed.
- Readability. Forty-character SHAs with a
# v4.2.1comment are hard to review and easy to let rot, so most teams quietly stop updating them.
The lockfile addresses all three. Each entry records the ref your workflow asked for, the commit it resolved to, and the numeric owner_id and repo_id of the source repository, so the runner can confirm the repository is still the one you locked. Per GitHub's docs, SHA pinning in workflow files becomes unnecessary once a repository uses a lockfile.
Adopt it in one repository
The tool is a GitHub CLI extension. It's pre-1.0, so the format and flags may change.
# Install the extension (requires an authenticated gh)
$ gh extension install github/gh-actions-lock
# From the repo root: pin every resolvable action and write actions.lock
$ gh actions-lock
$ git diff .github/workflows/ # review what got pinned
$ git add .github/workflows/ && git commit -m "ci: add workflow lockfile"
# After changing any uses: line, rerun to update the lock
$ gh actions-lock
# Advance moved refs (like v4 or main) to their current upstream commit, deliberately
$ gh actions-lock --relock
Review the generated diff like any dependency bump. Locking proves you'll run the same code every time; it doesn't prove that code is safe.
Then stop a stale or tampered lockfile from merging. The extension ships a verification workflow; a trimmed version:
# .github/workflows/verify-lockfile.yml
name: verify-lockfile
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
verify:
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@v7.0.1
with:
persist-credentials: false
- run: gh extension install github/gh-actions-lock --pin v0.2.0
- run: gh actions-lock --verify
Run gh actions-lock once more after adding this file so the verifier is itself locked. For a fast pre-commit hook, gh actions-lock --verify-local checks offline that every uses: has an entry. It checks coverage only, not provenance, so keep --verify in CI.
Roll it out across an org
Adoption is per workflow: once a workflow is onboarded, every dependency it uses must have a lockfile entry, and GitHub Actions runs the recorded commit. Admins at the enterprise, organization, or repository level can turn on the Require lockfile policy, also in public preview, to make lockfiles mandatory across their scope.
A sane order:
- Start with workflows that hold secrets or deploy. Release, publish, and deploy workflows are where a hijacked action does the most damage.
- Turn on Dependabot version updates for Actions. For onboarded workflows, Dependabot regenerates the matching lockfile entry in the same PR, so updates stay reviewable instead of manual.
- Enable Require lockfile per repository, then per org, once the verify job is green everywhere.
This pairs well with short-lived cloud credentials, so even a compromised step can't walk away with long-lived keys; see OIDC federation from GitHub Actions to AWS.
Where it still leaks
The extension's README is candid about current limits, and they matter for a threat model:
| Gap | Status in preview | What to do meanwhile |
|---|---|---|
Cross-repo reusable workflows (jobs.<id>.uses) | Not locked (issue #179) | Pin those calls to a full SHA |
Local ./ actions that call remote actions | Blocking error until you pass --migrate-local-actions or --ignore-local-actions | Migrate, then relock |
Container images (container:, docker://) | Not locked; images without @sha256: warn | Pin images by digest |
| Moving a floating ref back to an older commit | Not detected | Review lockfile diffs; prefer exact tags |
| GitHub Enterprise Server | Not supported | Keep SHA pinning |
The reusable workflow gap is the one to watch. GitHub's changelog says lockfiles cover "reusable workflow dependencies," but the extension's own README says cross-repository reusable workflows called at the job level aren't locked yet. Until that issue closes, assume they float. We covered the wider set of CI hardening controls after the npm worms in hardening CI against supply chain attacks.
Questions people ask
What is a GitHub Actions workflow lockfile?
It's a machine-generated file at .github/workflows/actions.lock that records, for every action a workflow uses, the requested ref, the exact commit it resolved to, and the source repository's numeric IDs. GitHub Actions runs the locked commit even if the tag or branch later moves. It's generated by the gh actions-lock CLI extension and is in public preview.
Do I still need to pin GitHub Actions to commit SHAs?
Not for workflows covered by a lockfile, according to GitHub's docs, because the lockfile pins direct and transitive actions and records repository identity. Keep SHA pinning where the lockfile doesn't reach yet: cross-repo reusable workflows, GitHub Enterprise Server, and container images, which you should pin by @sha256: digest.
Does Dependabot update the actions.lock file?
Yes. For workflows that are onboarded to a lockfile, Dependabot version updates regenerate the matching lockfile entry in the same pull request. You review one PR that changes both the uses: reference and its locked commit, with no extra Dependabot configuration needed.
The decision, concretely
- Repo with deploy or publish workflows? Adopt the lockfile this week and add
gh actions-lock --verifyto CI. - Already SHA-pinned everything? Adopt anyway; you gain transitive coverage and repo identity checks.
- Heavy use of cross-repo reusable workflows? Lock the rest, and keep SHA pins on those calls until issue #179 lands.
- On GitHub Enterprise Server? Not supported yet; stay on SHA pinning.
The call we'd make
Adopt lockfiles now on every repository that deploys or handles secrets, even in preview. The tool is pre-1.0, but the failure mode it fixes, a moved tag running unreviewed code with your credentials, has already happened at scale. Lock, verify in CI, let Dependabot drive updates, and pin the gaps by hand until GitHub closes them.
Sources
More from DevOps
Explore more articles in this category
GitHub Actions macOS 14 Retirement: Fix Your Builds Before Nov 2
GitHub retires the macOS 14 runner image on November 2, and brownouts are already failing jobs. Here is how to find every macos-14 reference and move without breaking Xcode.
PHP2Shell (CVE-2026-109618, CVE-2026-109619): Patch Before the Bots Do
Two unauthenticated RCEs in PHP core, leaked a day before the patch, with exploit attempts already logged. Here is how to tell if you are exposed and what to do tonight.
GitHub Copilot CLI Sandboxing Is GA: What Actually Changed
GitHub made local sandboxing generally available for Copilot CLI on October 7, so the default now restricts the agent instead of trusting it.
You might have missed
Evergreen posts worth revisiting.