Skip to main content
友田 陽大
Dependabot & dependency automation
Dependabot
GitHub Actions
サプライチェーンセキュリティ
DevSecOps
CI/CD
セキュリティ

Pin GitHub Actions to a SHA and update with Dependabot: a practice to prevent supply-chain attacks

A practical guide to pinning GitHub Actions' `uses:` from a mutable tag to a commit SHA and keeping it safely updated with Dependabot. Faithful to the official security-hardening guidance (as of June 2026), it explains, with real code, why tag references are dangerous, SHA pinning + version comments, Dependabot's behavior of updating the SHA and comment together, bulk-pinning an existing repository, and defense in depth — least-privilege GITHUB_TOKEN, allowing only trusted actions, and a policy enforcing SHA pinning.

Published
Reading time
7 min read
Author
友田 陽大
Share

uses: actions/checkout@v4 — almost every line of your CI is probably written like this. But that @v4 is "mutable." A tag can be reassigned to a different commit anytime. In other words, an action you audited and trusted today might morph into different code tomorrow. This is the core of GitHub Actions' supply-chain risk, and past actual breaches (theft of secrets via rewriting a popular action's tag) exploited exactly this structure.

The countermeasure is clear. Pin the action to a commit SHA (immutability) and keep it safely updated with Dependabot. This article explains that practice as a topic of the Dependabot production-operations guide.

Rules for this article: the recommended patterns are based on GitHub's official security-hardening guide (as of June 2026). Since CI security evolves fast in both attack techniques and countermeasures, always confirm the latest in the official Secure use reference before production application.


0. Why a tag reference is dangerous

GitHub Actions references have three levels, with different immutability.

ReferenceExampleImmutableRisk
Major tag@v4✗ mutableCan point to a different commit anytime. Most dangerous
Full-version tag@v4.2.0✗ mutableSemantic, but the tag can be reassigned
Commit SHA@a1b2c3... (40 chars)immutablePinned to audited code. Recommended

A tag is just a pointer. If an attacker hijacks an action's repository, they can redirect v4 to a backdoored commit. You changed nothing, yet malicious code runs on the next CI run — this is the attack that exploits "tag mutability."


1. SHA pinning = the only immutable reference

The countermeasure is to fix to the full commit SHA.

# Before(可変・危険)
- uses: actions/checkout@v4

# After(不変・推奨)— SHA に固定し、末尾コメントで版を可読に
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0

Why it's safe:

  • Immutable: the SHA is a hash of the commit contents. The code it points to never changes. It's fixed to the code at the moment you audited and trusted it.
  • Tamper-resistant: making different code appear as the same SHA requires producing a SHA-1 collision with a valid Git object, which is practically hard.
  • Readable: the trailing # v5.0.0 comment lets humans see the version at a glance. And this comment is rewritten by Dependabot on update (next chapter).

Use the 40-char full SHA, not a short SHA or a tag reference. This is the only way to use an action as an "immutable release."


2. Dependabot can update SHA pins

"If I fix it to a SHA, won't it stop updating?" — a common misconception. Dependabot understands SHA pinning.

When a new version comes out, Dependabot opens a PR that updates the SHA and the trailing version comment together. It's the exact same update experience as with a tag reference, and you can always stay with an immutable reference.

# Dependabot が開くPRの差分イメージ
- - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ - uses: actions/checkout@<新しいSHA> # v5.1.0

The configuration is just adding package-ecosystem: github-actions.

version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"           # .github/workflows/ を見る
    schedule:
      interval: "weekly"
    commit-message:
      prefix: "ci"
    groups:
      actions:
        patterns: ["*"]      # アクション更新を1PRに束ねる

"Immutability (SHA pin)" and "freshness (Dependabot)" aren't a trade-off; they can coexist — this is the most important point of this article. Take pinning's safety while updates catch up automatically.


3. Bulk-pin an existing repository

Hand-SHAing dozens of workflows isn't realistic. Use an auto-pinning tool.

  • pinact (OSS CLI): resolve uses: tags and bulk-convert to full SHA + version comment.
  • Services like StepSecurity's Secure Workflows / Harden-Runner also provide pinning and auditing.
# 例:pinact でリポジトリ内のアクションを一括ピン留め
pinact run

After introduction, Dependabot maintains and updates the commented SHAs, so once pinned, operations keep running. Adding a CI check that "fails if it detects an unpinned action" also prevents regression.


4. Defense in depth: don't rely on SHA pins alone

SHA pinning is powerful, but alone it's not a silver bullet. Layer it.

4.1 Least-privilege GITHUB_TOKEN

State and narrow the workflow's permissions. Don't leave the default broad permissions.

permissions:
  contents: read          # 既定を絞り、必要なジョブだけ昇格する

Even if an action is compromised, if what the token can do is small, the damage is limited. This is the same principle as in Dependabot's auto-merge workflow.

4.2 Allow only trusted actions

Restrict usable actions with the Actions policy at the organization/repository level. GitHub also provides a policy that requires SHA pinning (forcing allowed actions to be SHA references). Just narrowing the "anyone can uses: any action they like" state greatly reduces the attack surface.

4.3 Audit third parties

  • Official (actions/*, github/*) is relatively low-risk.
  • For third-party actions, audit the source before pinning, and check the star count, maintenance status, and permission requests.
  • Even after pinning, monitor for maintainer changes (below) in Dependabot PRs.

5. Auto-merge actions "carefully"

You can auto-merge action patch/minor too, but be one notch more careful from a supply-chain standpoint. The reason is that an action update is a change to the executing code of CI itself.

Use dependabot/fetch-metadata's maintainer-changes to route updates where the maintainer changed to human review. A maintainer change can be a sign of a hijack.

      - name: Hold actions PRs with maintainer changes
        if: |
          steps.meta.outputs.package-ecosystem == 'github_actions' &&
          steps.meta.outputs.maintainer-changes == 'true'
        run: |
          gh pr comment "$PR_URL" --body "⚠️ アクションのメンテナ変更を検知。供給網観点で人間レビュー必須。"
        env:
          PR_URL: ${{ github.event.pull_request.html_url }}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Auto-merge trusted first-party action patches
        if: |
          steps.meta.outputs.package-ecosystem == 'github_actions' &&
          steps.meta.outputs.maintainer-changes != 'true' &&
          steps.meta.outputs.update-type == 'version-update:semver-patch'
        run: gh pr merge --auto --squash "$PR_URL"
        env:
          PR_URL: ${{ github.event.pull_request.html_url }}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Policy: lean toward auto for official-action patches, humans for third-party and minor/major and maintainer changes. Automation proportional to risk.


6. Checklist

  • Pin all uses: to a full 40-char SHA + version comment (bulk with pinact, etc.)
  • Add package-ecosystem: github-actions to dependabot.yml and bundle with groups
  • State the workflow's permissions at least privilege
  • Restrict allowed actions with an Actions policy (enforce SHA pinning if possible)
  • Audit third-party before introduction, monitor maintainer-changes after
  • Limit action auto-merge to official patches, human review for the rest
  • Add a guard that "fails CI if it detects an unpinned action"

7. FAQ

Q. Doesn't SHA pinning stop updates? A. It doesn't. Dependabot understands SHA pinning and, when a new version comes out, opens a PR updating the SHA and version comment together. Immutability and freshness coexist.

Q. Isn't it enough to use Dependabot with @v4 as-is? A. You can follow updates, but the tag-mutability risk remains. A tag can be reassigned anytime, and if an attacker hijacks it, they can point v4 at a malicious commit. Only the SHA is immutable.

Q. Do official actions (actions/checkout, etc.) also need SHA fixing? A. The risk is relatively low, but pinning all actions for consistency is recommended. Since Dependabot maintains the updates, the operational load doesn't increase.

Q. How do I convert a large number of existing workflows? A. You can bulk-pin with an auto tool like pinact. It resolves tags into full SHA + comment, and Dependabot maintains them thereafter.

Q. May I auto-merge actions too? A. Lean toward auto for official-action patches, and route maintainer changes (maintainer-changes), third-party, and minor/major to human review. Since an action update is a change to CI's executing code, be one notch more careful.

友田

友田 陽大

Developer of a METI Minister's Award–winning product. With TypeScript + Python + AWS, I deliver SaaS, industry DX, and production-grade generative AI (RAG) end to end — from requirements to infrastructure and operations — single-handedly.

I can take on the implementation from this article as an engagement

Dependency auto-updates & supply-chain defense, from design to production

From enabling alerts/security/version updates, to dependabot.yml design (groups, cooldown, monorepo directories, private registries), safe auto-merge via GitHub Actions + fetch-metadata (auto for patch/minor, human review for major), severity-based-SLA vulnerability response, and observability of the open count. With experience wiring dependency updates into CI quality gates, I implement automation that doesn't flood you with PRs or pile up technical debt.

Available for both project-based (contract) and advisory engagements. Start with a free 30-minute consult.

Also worth reading