That GitHub Action pinned to @main is running whatever the maintainer pushes today
Par Seaworthy · 17 août 2026
Photo by Markus Spiske on Unsplash
A generated CI workflow file will typically include a step like this:
- uses: some-org/some-action@main
or, just as often:
- uses: some-org/some-action@v3
Both look pinned. Neither actually is. @main tracks whatever the latest commit on that branch happens to be at the moment your workflow runs, and @v3 is a tag, which the repo owner can move to point at a different commit at any time without changing its name. In both cases, the code that executes inside your CI pipeline today can be different from the code that ran yesterday, and you have no record of what changed unless you go looking.
Why this matters more than an ordinary dependency
A vulnerable npm package sitting in your node_modules is a real risk, but it's a risk you can audit: npm audit, lockfiles, and lockfile diffs all give you a paper trail. A GitHub Action referenced by branch or tag has none of that. It runs with access to your repository's secrets, your GITHUB_TOKEN, and in many setups the ability to push commits or tags back to your repo. If the maintainer's account is compromised, or the maintainer themselves decides to slip something malicious into the next commit on main, every workflow that references @main picks it up on its very next run, with no version bump, no changelog entry, and no PR for anyone to review.
This isn't hypothetical. Several popular actions have had exactly this happen: a maintainer account got compromised, malicious code landed on a tracked branch or a moved tag, and every downstream CI pipeline referencing it executed that code with full access to whatever secrets the workflow had available, including npm publish tokens and cloud credentials in some documented cases.
Pin to a commit SHA, not a version
- uses: some-org/some-action@8f4b7f84864484a7bf31766abe9204da3cbe65b # v3.1.0
A commit SHA can't be moved. Whatever code was at that SHA when you pinned it is the code that runs, every time, until you deliberately change the pin. The trailing comment with the human-readable version is just for your own sanity when reading the file later, GitHub Actions ignores it entirely.
Updating a SHA-pinned action takes one extra step compared to bumping a version tag: you look at the diff between your current commit and the new release before you update, the same way you'd look at a dependency changelog before a major version bump. That extra step is the entire point. It turns an automatic, invisible update into a reviewed one.
Dependabot handles the update part
If manually tracking SHAs sounds tedious, Dependabot supports GitHub Actions the same way it supports npm or pip:
# .github/dependabot.yml
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
This opens a PR with the new SHA and a diff link whenever a pinned action has an update, which gets you both the security benefit of pinning and a low-effort path to actually staying current, instead of pinning once and never touching it again.
Actions aren't the only mutable reference in your pipeline
The same problem shows up wherever a build references something by a name that isn't actually fixed. A FROM node:20 in a Dockerfile pulls whatever node:20 currently points to, which changes as new patch versions ship, rather than the image you tested against. A curl https://example.com/install.sh | bash step in a workflow runs whatever that script happens to contain at execution time, with no diff to review at all. None of these are exotic, they're all common patterns in generated build and deploy configs, and all of them share the same fix in spirit: pin to something immutable, an image digest, a commit SHA, a specific package version, and let a bot open a PR when you actually want to move forward.
Why AI-generated workflows default to branch or tag references
Copying @v3 or @main from an action's own README is the path of least resistance, and it's genuinely fine advice for a human reading a quickstart who's going to glance at the action before using it. An assistant generating a CI file has no such review step baked in: it produces syntactically valid, functionally correct YAML, and @main is functionally correct in the sense that the workflow runs. Whether it's supply-chain safe is a separate question the generation step never asks.
Seaworthy flags GitHub Actions referenced by branch name or mutable tag instead of a commit SHA, since it's one of the few CI risks that's both high-impact and fixable without touching any application code.
Cet article a été généré par IA et résume des sources publiquement disponibles.
Seaworthy analyse votre dépôt pour détecter les problèmes abordés dans des articles comme celui-ci.
Failles de sécurité, secrets exposés et mauvaises configurations détectés avant le déploiement. Gratuit, aucun compte requis.