All articles
Dev ToolsBy Petru Popa · 6 min read

GitHub Actions Security: What the New Workflow Lockfile Does Not Lock Yet

GitHub put workflow dependency lockfiles into public preview on 14 October. The gh-actions-lock repository's source and its first week of issues show three kinds of dependency the lockfile skips without saying so, and one case where a broken file fails open.

The standard GitHub Actions security advice has been to pin every third-party action to a full commit SHA. On 14 October GitHub offered a replacement. Workflow dependency lockfiles, in public preview for Enterprise Cloud, Enterprise Cloud with data residency, Team and Free, keep a file at .github/workflows/actions.lock that records the commit each action reference resolved to, transitive ones included. The runner runs that commit even if the tag moves. The docs say a repository that adopts it no longer needs SHA pins in its YAML.

A GitHub CLI extension, gh-actions-lock, generates the lockfile, so its code decides what gets covered. This post reads it at v0.2.0, the current release, along with its issue tracker.

What gh-actions-lock does

Run gh actions-lock from a repository root. It resolves every uses: reference under .github/workflows/, rewrites loose tags like v3 to the exact version they resolve to, and writes each dependency with its ref, its commit, and the numeric owner and repository IDs. According to the README, every locked pin is checked the way the runner checks it at job start: the repository was not renamed or transferred, the commit still exists, and full-version tags still point at it. The docs add that a locked commit must sit on a branch of the source repository, which is aimed at impostor commits served from forks. --verify runs these checks in CI without writing anything.

A SHA pin in YAML covers only the action you named, not the actions it calls in turn. The lockfile follows those calls.

Maturity signals

The repository is MIT licensed Go. Its first release, v0.0.1, came out on 23 April, and v0.2.0 on 12 October. The README calls it pre-1.0 and warns that the lockfile format, flags and behavior can change between releases. GitHub Enterprise Server is not supported. When this was written it had 227 stars and 21 open issues that are not pull requests, seven of them filed on or after 14 October, when the preview opened. Three of the four gaps below were reported in those days.

Four gaps the issue tracker found

Remote reusable workflows are not locked. The concept page says the lockfile pins every action and reusable workflow a workflow depends on. In the v0.2.0 source, a job-level uses: value goes to the shared library's ParseActionRef. That function returns nothing for any path under .github/workflows/, and the scanner throws the empty result away. The library also has a ParseReusableWorkflowRef function, and the CLI never calls it. Issue 179 shows the result: a workflow that only calls a tagged reusable workflow gets an empty entry, and --verify passes. A maintainer closed the earlier issue 129 on 15 October with a note that pinning job-level references is not planned for now.

I read the first 40 lockfiles a GitHub code search returned. Three repositories (hashintel/hash, hashintel/labs and guardian/cdk) call remote reusable workflows, each recorded as an empty list. All three had kept the full SHA pin in the YAML. Those adopters are running two pinning schemes side by side, and the lockfile only knows about one.

Container images still run by tag. At the commit tagged v2.4.4, ossf/scorecard-action pulls ghcr.io/ossf/scorecard-action:v2.4.4, which is a tag. Locking the action's commit leaves that tag free to move. The author of issue 168 scanned 1,802 active public repositories (not a random sample) and found 177 using an action that pulls a prebuilt image by tag. On issue 128 a maintainer wrote that runtime enforcement of Docker images has no reasonable path today, and suggested documenting where the lockfile's trust stops.

Parallel step groups are skipped. In workflowfile.go, the step walker reads uses: only from entries directly under steps:. Actions nested under a parallel: group never reach the lockfile. Issue 175 reported it on 16 October; the fix, pull request 176, had not merged at the time of writing.

A broken lockfile fails open. In issue 125, a corrupt lockfile was simply ignored and the workflows ran. The maintainer replied that failing closed would break workflows that were never onboarded, and that the Require lockfile policy is the switch that makes a broken lockfile fail closed.

A fifth report, issue 174, had --verify accept a hand-edited entry for a fork-only commit, which a hosted runner then executed. A maintainer said on 16 October that the backend was fixed and the CLI would follow. Until then, a passing --verify does not prove the lockfile was never edited by hand.

Who should not adopt it yet

GitHub Enterprise Server shops, because the tool does not support them. Teams whose risk sits mostly in shared reusable workflows from other organizations, because those calls fall outside the lockfile. Organizations that use an allowed-actions list, until someone rewrites it. The how-to guide notes that once a workflow is onboarded, the allowlist is checked against the locked SHA, so tag entries like actions/checkout@v6 stop matching.

My view: adopt it for transitive action coverage, which SHA pins never gave you, and keep every SHA pin you already have. Nothing in v0.2.0 makes those pins redundant for reusable workflows or images. This claim is wrong as soon as a release locks job-level uses: and --verify reports unlocked image tags.

GitHub's own enforcement guide is a pilot plan: Evaluate mode, automations to keep the file current, a CODEOWNERS rule, then Active, plus a Copilot skill that the guide calls a convenience rather than a control. The pilot proves the mechanism; production depends on maintenance nobody budgeted for, which is the argument in From PoC to Production. Moving tags are not a hypothetical risk either: the breaking change behind the pull_request_target deadline reached builds through moving actions/checkout tags.

Three checks before you onboard

All three were tested against real workflow files during this run. The first lists remote reusable workflow calls that are not pinned to a full SHA, which the lockfile will not cover. The second finds parallel step groups, whose actions v0.2.0 skips. The third shows the image a locked Docker action really pulls (some actions name the file action.yaml).

grep -nE '^ +uses: +[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+/\.github/workflows/' .github/workflows/*.y*ml | grep -vE '@[0-9a-f]{40}'

grep -nE '^ +- parallel:' .github/workflows/*.y*ml

gh api 'repos/OWNER/REPO/contents/action.yml?ref=LOCKED_SHA' --jq .content | base64 -d | grep -n 'image:'

The first should come back empty: anything it prints is a reusable workflow on a moving ref that the lockfile will not catch. In the third, any image without @sha256: is still floating. Then add actions.lock to CODEOWNERS, pilot the Require lockfile policy in Evaluate mode, and move it to Active. Only the active policy makes a broken file fail closed.

Ready to start?

Turn this into a plan for your team.

One week, fixed fee: a working session with your team, a prioritized use-case backlog, and an ROI model for the opportunities worth chasing.

Book an AI Opportunity Sprint