In March 2026, attackers hijacked 75 of 76 version tags in aquasecurity/trivy-action — the security scanner developers trust to find vulnerabilities — and quietly rerouted credentials from 10,000+ CI workflows to a typosquatted domain. No YAML changes required in any of the victim repos. Just tag math.
Last week, GitHub shipped workflow lockfiles in public preview. They won’t close every hole, but they close the specific hole that made that attack — and the tj-actions/changed-files attack before it — structurally possible. If your team runs GitHub Actions and hasn’t acted on CI supply chain security yet, this is the week to start.
Why Floating Tags Are a Structural Problem
Git version tags are mutable. When you write uses: actions/checkout@v4, you are not pinning to a commit — you are following a pointer that the upstream maintainer can silently reassign to any commit they choose. Attackers who compromise a maintainer account can rewrite every version tag simultaneously. The victim repos see nothing unusual in their workflow YAML. The change happened upstream.
That is exactly what happened in March 2025 with tj-actions/changed-files (CVE-2025-30066): tags v1 through v45.0.7 were rewritten to a single malicious commit that dumped CI/CD secrets into public workflow logs. 23,000+ repositories were affected. In May 2026, a group called TeamPCP compromised roughly 3,800 of GitHub’s own internal repositories using the same pattern at scale.
What a Lockfile Actually Does
GitHub’s solution creates a .github/workflows/actions.lock file in your repository. It records the exact commit SHA for every action in your workflows, including actions called by composite actions — the transitive dependencies that manual SHA pinning has always missed.
The lockfile also stores numeric owner and repository IDs alongside commit SHAs. That detail matters: it blocks rename and transfer attacks, where an abandoned action name gets claimed by a different account and the repo identity shifts without changing the tag. Locked commits must also exist on real repository branches, which closes the fork-injection technique used in some 2026 attacks.
Your workflow YAML keeps its human-readable versions: uses: vendor/build-action@v3 stays exactly as written. The lockfile handles the commit binding. When the upstream maintainer publishes v3.1.0, Dependabot updates both the YAML reference and the lockfile entry in one PR.
Setting It Up
Three commands get you started:
gh extension install github/gh-actions-lock
gh actions-lock lock .github/workflows/your-workflow.yml
gh actions-lock validate
Start with one low-risk workflow — a lint check or unit test run, not your production deploy. The extension rewrites partial tags (@v3 to @v3.1.0) and converts local action syntax (./ to $/), so the initial diff is larger than you might expect. Review every resolved SHA against what you expect before committing.
Add gh actions-lock validate as a CI step before you make the lockfile required. This surfaces stale entries as PR failures rather than silent drift. Organization and repository admins can enable a Require lockfile policy once all workflows are onboarded — the policy covers all plan types, including Free.
The Gap: Docker Actions Are Still Floating
Lockfiles pin commit SHAs for JavaScript and composite actions. They do not pin container image tags. Actions that use docker://ghcr.io/org/scanner:v2 syntax still reference a moving pointer — the same attack surface the trivy-action incident exploited. If your pipeline uses container-based actions, you still need to pin those image digests manually.
Two other limits: this is a Cloud-only feature for now, so GitHub Enterprise Server teams are not covered. And it is a public preview — the CLI flags and lockfile schema may change before GA.
Deploy it anyway. The majority of the actions compromised in the tj-actions and trivy-action attacks were JavaScript-based — exactly what lockfiles cover. Partial coverage beats none, and the setup takes under 30 minutes for a single workflow.
Rolling Out Without Breaking CI
A few gotchas before enabling the Require policy:
- Tag-based allowlists stop matching after locking — update them to use SHAs or wildcards
- Filtered-path required checks: if your validate workflow uses
paths:filters and becomes required, unrelated PRs will get stuck when the filter skips execution - Partially onboarded repos: the validate tool treats missing entries and unused entries as failures — do not make locking required until every workflow is onboarded
- Reusable workflows: the repository hosting the reusable workflow needs its own lockfile
The practical path: lock one workflow, review the diff, run validate in CI for a week, onboard remaining workflows, enable the Require policy last. Two to four weeks for a typical team. The official changelog entry links to full documentation.













