skip to content

Why pin a third-party GitHub Action to a full commit SHA instead of a version tag?

level: middleimportance: should knowfreq 58%

answer

  1. Ask what a tag really is
  2. Major tags are supposed to move
  3. Content-addressed versus a pointer
  4. The cost is staleness, not safety
  5. Automation closes the update gap

basics

~20 s

Git tags are mutable and major tags like v4 are deliberately re-pointed on each release, so uses: acme/action@v4 can execute different code tomorrow. A full commit SHA is immutable, so the code you reviewed is the code that runs.

solid answer

~40 s

`uses: acme/action@v4` resolves a tag at run time, and tags are mutable: the maintainer moves `v4` forward on every release, and any tag can be force-pushed — including by an attacker who compromises the repository or its maintainer account. So a workflow that passed review can silently run new, unreviewed code, with your job's environment and any secrets you pass it. Pinning `uses: acme/action@<full 40-character SHA>` freezes the exact tree; a compromised tag cannot redirect it. Convention is to keep the human-readable version in a trailing comment so the pin is legible and tooling can bump it. The cost is real: pins do not receive upstream fixes automatically, so you pair SHA pinning with an automated updater and treat the bump like any other dependency change.

code

yaml · 6 lines
yaml
steps:
  # floating major tag: maintainer re-points it on every release
  - uses: actions/checkout@v4

  # pinned: exactly the tree that was reviewed, version kept legible
  - uses: actions/checkout@<full 40-character commit sha> # v4.2.2

go deeper

for a junior

Know that @v4 is a tag that maintainers move and that a commit SHA identifies one exact version of the code.

for a middle

Explain mutability concretely: which refs can change, how a moved major tag reaches every consumer, and how the version comment plus an updater keeps pins fresh.

for a senior

Argue the policy: pin third-party actions, scope the job's token and secrets anyway, and make staleness an automated-update problem rather than an argument against pinning.

for a principal

Own enforcement across the organisation — a lint or ruleset that rejects unpinned third-party references, an allow-list, and the update pipeline that keeps hundreds of repositories from freezing on old pins.

## What a ref actually resolves to `uses: owner/repo@ref` fetches that repository at `ref` and executes it inside your job. The ref can be a branch, a tag, or a commit SHA — and only the last is immutable. - `@main` — whatever is on the branch when the workflow runs. Maximum drift. - `@v4` — by strong convention in the Actions ecosystem, a **moving major tag** that maintainers re-point at each `v4.x.y` release. It is designed to move. - `@v4.2.2` — usually stable in practice, but a tag is just a pointer; it can be deleted and recreated at a different commit. - `@<40-char SHA>` — a content-addressed commit. It cannot be changed under you. ## The threat model An action is arbitrary code running in your job. It sees the workspace, the environment, network egress, and any secret you pass it — including `GITHUB_TOKEN` if it reads the environment. Two realistic paths to compromise: 1. **Maintainer or repository compromise.** An attacker with push access moves `v4` to a malicious commit. Every consumer using the floating tag picks it up on their next run, with no diff to review and nothing changed in their own repository. 2. **Ownership transfer or typosquatting drift.** A popular action changes hands; the new owner's release is not the code you audited. SHA pinning defeats the first outright: your workflow names a commit that predates the attack. It does not defeat everything — the *initial* commit you pinned could itself be malicious, and a pinned action can still pull in a compromised dependency at run time — which is why pinning is a control, not a guarantee. ## Why teams resist, and the answer The honest objection is maintenance: a SHA pin is opaque and never updates itself, so you can sit on an action with a known bug for a year. The standard resolution is automation. Keep the version in a trailing comment: ```yaml - uses: actions/checkout@<full 40-character commit sha> # v4.2.2 ``` Dependabot's `github-actions` ecosystem understands this form: it bumps the SHA and rewrites the comment, so you get a reviewable pull request per upstream release instead of silent adoption. That turns "never updates" into "updates through the same review gate as every other dependency". ## Where the bar sits A reasonable, widely used policy: - **Third-party actions: pin by SHA, always.** These are the supply-chain risk. - **Actions you own inside the organisation: a major tag is usually acceptable**, because the same people control the source and its branch protections; some organisations still pin everything for uniformity. - **Never `@main` on anything you did not write**, and be wary of it even then. ## Verifying and enforcing The SHA in a workflow can be checked against the upstream repository, and repository-level scanning or a lint job can reject non-SHA references in `.github/workflows`. GitHub is also moving toward immutable releases for actions published to the Marketplace, which narrows the tag-mutation window for those actions — worth knowing, but not a reason to stop pinning today. ## The one-line answer interviewers want "Tags are pointers and major tags move by design, so a tag reference is a promise the maintainer can rewrite. A SHA is content-addressed. I pin third-party actions by SHA with a version comment and let an automated updater raise the bumps as reviewable pull requests."

  • Does pinning to a SHA make a third-party action safe?
    No, it makes it *fixed*. You still ran whatever that commit contains, and the action can fetch code or dependencies at run time from outside the pin. Pinning removes silent substitution; you still need to review what you pinned, restrict the job's permissions, and avoid passing secrets the action does not need.
  • How do you avoid SHA pins going stale?
    Automate the bumps. Dependabot's github-actions ecosystem updates SHA pins and rewrites the trailing version comment, so each upstream release arrives as a reviewable pull request. Without that, pinning trades a supply-chain risk for an unpatched-bug risk, and teams quietly abandon it.
  • Is pinning your own organisation's actions by SHA worth it?
    Usually less critical: you control the source repository, its branch protections and who can move tags, so a major tag carries far less risk. Some organisations still pin uniformly so that a single lint rule covers every reference and no reviewer has to judge whether a given action is internal.

saying these in an interview costs you the question

  • Believing a tag cannot be moved
  • Thinking @v4 pins a specific release
  • Treating an action as data rather than executable code
  • Pinning by SHA and never updating anything again
  • Using @main for third-party actions to stay current

context