Enforcing SHA pinning for GitHub Actions via settings: testing what gets blocked, and whether tags can still pass with a lockfile

Enforcing SHA pinning for GitHub Actions via settings: testing what gets blocked, and whether tags can still pass with a lockfile

GitHub Actions can enforce SHA pinning through settings, and it blocked tag references, including those inside composite actions. I confirm the GA workflow lockfile lets tags pass, and compare it with rewriting to full SHAs.
2026.11.02

This page has been translated by machine translation. View original

Hello! I'm Yuji Nishimura from the Operations Department!

Pinning GitHub Actions actions to a full commit SHA (hereafter, full SHA) instead of a tag like actions/checkout@v7 is often recommended as a supply chain measure. However, if reviewers overlook it, tag references can slip in and remain. GitHub has a policy, "Require actions to be pinned to a full-length commit SHA", that prohibits actions not pinned to a full SHA through a setting.

In this article, I check what is blocked and what passes when this setting is enabled, for each way of writing a reference. I also test whether the blocked ones can be passed while keeping the tag by using a workflow lockfile, and sort out when to rewrite to a full SHA and when to use a lockfile.

I checked this on November 2, 2026. The environment was a private repository created in a Free plan account and a GitHub-hosted ubuntu-latest runner. I did not test the following conditions, so they cannot be judged from the results of this article.

  • Enabling it all at once for an Organization or Enterprise
  • Combinations with an allow list (Allow select actions) or the Require lockfile policy that makes lockfiles mandatory
  • Reusable workflows in other repositories, or third-party composite actions
  • Images specified in a job's container: or services:
  • Events other than push, or self-hosted runners

Conclusion first: what is blocked and what passes

These are the results of running one workflow for each way of writing a reference, with enforcement enabled.

Notation Example Result
Tag actions/checkout@v7 Blocked
Branch actions/checkout@main Blocked
Short SHA actions/checkout@3d3c42e Blocked (cannot be used even without enforcement)
Full SHA actions/checkout@3d3c42e5… (40 digits) Passes
Action in the same repository ./.github/actions/hello, $/.github/actions/hello Passes (out of scope)
docker:// step docker://alpine:3.20 Passes (out of scope)
Calling a reusable workflow …/reusable.yml@v1 Passes (out of scope)
Tag reference inside a reusable workflow actions/checkout@v7 in the called workflow Blocked
Tag reference inside a composite action pinned to a full SHA actions/checkout@v7 inside Blocked
Workflow onboarded with a lockfile actions/checkout@v7 and the composite action's @v1, left as tags Passes (including tag references inside the composite action)

$/ is a reference that points to the running commit in the same repository as the workflow. Onboarding means recording the workflow's dependencies in the lockfile with gh actions-lock.

The thing to watch out for is tag references inside composite actions and reusable workflows. Even if you pin your own workflow to a full SHA, a tag reference there will cause it to be blocked. On the other hand, a workflow onboarded with a lockfile passed even down into the composite action while keeping tags. If you are starting now, it seems best to center on the lockfile and combine it with this enforcement. I sort out how to choose between them in the last section.

The following figure is a conceptual diagram I made myself, not an official architecture diagram. It organizes the results in the table by where the enforcement check applies. Red boxes are references that required a full SHA, and gray dotted boxes are references that were out of scope. A workflow onboarded with a lockfile fetches the range enclosed by the green dotted line using the commits recorded in the lockfile, so it passes even with tags.

A self-made conceptual diagram of where SHA pinning enforcement applies. Four arrows come out of the workflow. The step's uses and the uses inside the composite action beyond it require a full SHA. A reusable workflow called with the job's uses can be called by tag, but the uses inside the reusable workflow require a full SHA. Actions in the same repository (./ and $/) and docker:// steps are out of scope. A workflow onboarded with a lockfile fetches the step's uses and the uses inside the composite action with the commits recorded in the lockfile, and passes even with tags

Enable it from the UI or the API

Enable it from the UI

Open the repository's "Settings" → "Actions" → "General", and you will find a checkbox below the radio buttons of "Actions permissions". The red frame was added by me.

The Actions, General screen in the repository's Settings. The checkbox "Require actions to be pinned to a full-length commit SHA" below the Actions permissions radio buttons is enclosed in a red frame and is turned on

This checkbox was present even in a repository of a Free plan account. According to the Changelog, it is not shown when Actions is disabled. The documentation says that if it is overridden by a higher-level policy such as an Organization's, it may not be changeable on the repository side.

Enable it with the API

In the REST API, you switch it with sha_pinning_required. For a single repository, it looks like this. This time I also specified allowed_actions with its current value (all).

gh api -X PUT repos/<owner>/<repo>/actions/permissions \
  -F enabled=true -f allowed_actions=all -F sha_pinning_required=true

When I fetched it after setting, sha_pinning_required was true.

gh api repos/<owner>/<repo>/actions/permissions
{"enabled":true,"allowed_actions":"all","sha_pinning_required":true}

The documentation says the same field can also be specified with the Organization-level PUT /orgs/{org}/actions/permissions (I did not test at the Organization level).

Testing with workflows that change the reference notation one at a time

In a verification repository, I placed 14 workflows that each change the way of writing the reference one at a time. The workflows that test step actions change only the uses: line from the following tag reference example. Reusable workflows are called with the job's uses:.

.github/workflows/t01-tag.yml
name: t01-tag
on: [push, workflow_dispatch]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7

To test a tag reference inside a composite action, I also prepared a composite action that uses actions/checkout@v7 inside. Referencing it with ./ would put it out of scope for enforcement, so from the workflow I reference it with a full SHA in the same owner/repo/path@SHA form as actions in other repositories.

.github/actions/composite-tag/action.yml
name: composite-tag
description: Composite action that uses another action by tag
runs:
  using: composite
  steps:
    - uses: actions/checkout@v7
    - run: echo "inner checkout done"
      shell: bash
.github/workflows/t10-composite-inner-tag.yml
name: t10-composite-inner-tag
on: [push, workflow_dispatch]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: ynishimura/blog-verify-sha-pinning/.github/actions/composite-tag@b49117c7d7390c3bcda8b7321ae7b75b85686529

First, I ran the 11 workflows t01 to t11 with enforcement still disabled and confirmed that all but the short SHA one succeeded. After that, I enabled enforcement, pushed an empty commit, and ran the same 11. The tag reference inside a reusable workflow (t12) and the workflows onboarded with a lockfile (t13, t14) were added after enabling enforcement.

What is blocked and what passes when enforced

Tags, branches, and short SHAs are blocked at the job preparation stage

Workflows that referenced actions by tag, branch, or short SHA failed at the job's "Set up job". The job itself is created, but it hits the following error at the stage of fetching the action's download information. The red frame was added by me.

The run screen of t01-tag.yml after enabling enforcement. Status is Failure, and the error "The action actions/checkout@v7 is not allowed in ynishimura/blog-verify-sha-pinning because all actions must be pinned to a full-length commit SHA." is shown in Annotations, enclosed in a red frame

##[error]The action actions/checkout@v7 is not allowed in ynishimura/blog-verify-sha-pinning because all actions must be pinned to a full-length commit SHA.

@main and @3d3c42e had the same message, differing only in the reference part.

A short SHA fails even with enforcement disabled. In the run before enabling enforcement, it was the following error asking to use a full SHA.

##[error]Unable to resolve action `actions/checkout@3d3c42e`, the provided ref `3d3c42e` is the shortened version of a commit SHA, which is not supported. Please use the full commit SHA `3d3c42e5aac5ba805825da76410c181273ba90b1` instead.

Actions in the same repository and docker:// steps are out of scope

Workflows with local references like ./.github/actions/hello and with the $/ reference succeeded even with enforcement enabled. Since $/ resolves to the running commit, it is fetched by full SHA in the log as well.

Download action repository 'ynishimura/blog-verify-sha-pinning@c886d0f25fc872848150f0b5b2fdba2175fbc1a7' (SHA:c886d0f25fc872848150f0b5b2fdba2175fbc1a7)

Regarding local references, a report that they are blocked when enforcement is enabled was posted in August 2025 in GitHub Community Discussion #170337. In the same thread, on November 3, 2025, a user replied that it seemed to have been fixed and that in their own organization they could use local actions without pinning them by SHA. I could not find an announcement in the official Changelog. In my environment as of November 2, 2026, local references were not blocked.

A docker:// step that specifies the image by tag, such as docker://alpine:3.20, also succeeded. If you want to pin the image, it seems you need to specify the digest yourself, like alpine:3.20@sha256:… (I did not test specifying a digest).

Reusable workflows can be called, but actions inside them are blocked

A workflow that calls a reusable workflow by tag like @v1 in the job's uses: succeeded. The documentation also says "Reusable workflows can still be referenced by tag."

However, when the called reusable workflow used actions/checkout@v7 inside, the calling workflow failed. The error appeared in the called workflow's job (call / inner), and the message was the same as for tag references. This time, the called reusable workflow was also placed in the same repository with enforcement enabled. For a reusable workflow in another repository, I did not isolate whether the judgment is based on the caller's or the callee's setting.

Composite actions are blocked by tag references inside

Even when a composite action was referenced by full SHA, it failed if it used actions/checkout@v7 inside. Looking at the log, the outer composite action was downloaded. After that, it stopped at the stage of resolving the inner action.

Download action repository 'ynishimura/blog-verify-sha-pinning@b49117c7d7390c3bcda8b7321ae7b75b85686529' (SHA:b49117c7d7390c3bcda8b7321ae7b75b85686529)
Getting action download info
##[error]The action actions/checkout@v7 is not allowed in ynishimura/blog-verify-sha-pinning because all actions must be pinned to a full-length commit SHA.

As a control, a workflow that referenced by full SHA a composite action that uses no other action inside (only a run step) succeeded. From this, we can tell that the cause of the block was the tag reference inside. What I tested was a composite action in my own repository, but a Zenn article reports a case where a third-party composite action was blocked.

Passing with tags using a lockfile

A workflow lockfile is a mechanism that records, in .github/workflows/actions.lock, the actions a workflow uses and the commits of the actions used inside them. It became generally available on October 28, 2026. The documentation says, "Locked dependencies satisfy Require actions to be pinned to a full-length commit SHA without changes to the workflow file." This means that a workflow onboarded with a lockfile satisfies the enforcement requirement without being rewritten to a full SHA. I tried how lockfiles work in a previous article.

It passed with tags, even inside the composite action

I prepared a workflow that references actions/checkout with @v7 and the composite action from earlier with @v1. Both are tag references. I onboard only this workflow with gh actions-lock v0.4.0 (installable with gh extension install github/gh-actions-lock).

gh actions-lock .github/workflows/t14-locked-fresh.yml
Scanning 1 workflow
Planning pins
Pinned 2 actions across 1 workflow
  actions/checkout@v7 (3d3c42e)
    └─ .github/workflows/t14-locked-fresh.yml
  ynishimura/blog-verify-sha-pinning@v1 (b49117c)
    └─ .github/workflows/t14-locked-fresh.yml
1 action pinned without a full semver tag
  actions/checkout@v7 → latest: v7.0.1
    .github/workflows/t14-locked-fresh.yml
  Update the uses: line in your workflow to the full version to lock precisely.

In this workflow, @v7 was not rewritten and was recorded in the lockfile as is. Instead, a warning recommending rewriting it to the full version tag (@v7.0.1) was shown.

However, this does not mean that v0.4.0 always leaves @v7 as is. According to the README, partial versions like @v7 are narrowed down to full tags, and adding --no-narrow leaves them as written. The reason it was left this time is that the composite action also uses the same actions/checkout@v7 inside. The gh actions-lock source has logic that leaves references declared by a composite action unrewritten.

In the lockfile, the actions/checkout@v7 used inside is also recorded as the uses of the composite action's entry. The following is an excerpt of only the parts related to t14 from the final lockfile. The actions/checkout@v7 entry was added when I recreated t13 (described later), and what t14's onboarding added is the workflows part. My own repository's IDs are masked.

.github/workflows/actions.lock(excerpt)
workflows:
    '.github/workflows/t14-locked-fresh.yml':
        - 'actions/checkout@v7'
        - 'ynishimura/blog-verify-sha-pinning@v1'
dependencies:
    'actions/checkout@v7':
        ref: 'v7'
        commit: 'sha1-3d3c42e5aac5ba805825da76410c181273ba90b1'
        owner_id: 44036562
        repo_id: 197814629
    'ynishimura/blog-verify-sha-pinning@v1':
        ref: 'v1'
        commit: 'sha1-b49117c7d7390c3bcda8b7321ae7b75b85686529'
        owner_id: <owner_id>
        repo_id: <repo_id>
        uses:
            - 'actions/checkout@v7'

When I pushed with enforcement still enabled, this workflow succeeded. In the log, a message about using the lockfile was followed by the two actions being fetched with the commits recorded in the lockfile. The actions/checkout@v7 inside the composite action is the same reference as the one referenced directly from the workflow, and there was only one download line.

Using locked action versions from the workflow's lockfile
Download action repository 'actions/checkout@v7' (SHA:3d3c42e5aac5ba805825da76410c181273ba90b1)
Download action repository 'ynishimura/blog-verify-sha-pinning@v1' (SHA:b49117c7d7390c3bcda8b7321ae7b75b85686529)

The non-onboarded workflows that ran on the same push remained failures. They are the tag-reference workflow (t01) and the workflow that references the same composite action by full SHA (t10). The documentation also says, "Dependencies that aren't in the lockfile still need a full-length SHA."

Recreate lockfiles made with v0.2.0

At first, I had onboarded with gh actions-lock v0.2.0 (t13). Even though the composite action used the same reference, v0.2.0 rewrote the workflow's @v7 to @v7.0.1, and recorded even the actions/checkout@v7 inside the composite action as actions/checkout@v7.0.1. With this lockfile, the workflow failed at runtime with the following error.

Using locked action versions from the workflow's lockfile
##[error]lockfile missing pin for actions/checkout@v7

The composite action says @v7 inside, but the lockfile only has @v7.0.1, so it reads as if the lookup could not match. The message differs from the enforcement error, and it is a failure caused by the onboarded workflow's dependencies not being complete in the lockfile (I did not test this with enforcement disabled). When I upgraded to v0.4.0 and ran gh actions-lock --verify, this lockfile was detected as incomplete-lockfile. Because I ran it without arguments, errors for other, non-onboarded workflows also appeared, but the part related to t13 is as follows.

✗ Lockfile doesn't match locked manifest actions/checkout@v7
  ynishimura/blog-verify-sha-pinning/.github/actions/composite-tag@v1 at locked commit b49117c7d739 uses actions/checkout@v7, but its lockfile entry ynishimura/blog-verify-sha-pinning@v1 does not record it
  ! restore the edge from version control, or run `gh actions-lock --relock`
  see: https://github.com/github/gh-actions-lock/blob/main/docs/troubleshooting.md#incomplete-lockfile

The v0.3.0 release notes also say that old lockfiles may become incomplete-lockfile with --verify. The fix is gh actions-lock --relock. When I actually recreated it, t13 also succeeded with enforcement still enabled. For lockfiles created back in the v0.2.0 days, I decided to update to the latest with gh extension upgrade actions-lock, run --verify, and recreate with --relock if incomplete-lockfile appears.

What to check before enabling it, and how to choose

This setting only stops violating workflows. It does not rewrite to a full SHA or update to new releases. Rewriting and updating require separate tools such as pinact or Renovate.

Based on these results, it seems you need to check the following two points before enabling it.

  1. Whether there are tag references inside the composite actions and reusable workflows you use
    • For a composite action with tag references, you would either onboard it with a lockfile or wait for an upstream fix
    • Regarding the inside of reusable workflows, Locking action dependencies says that the lockfile of the called repository, not the caller, is what applies. I did not test whether it passes when onboarded in the called repository
  2. That on the Free plan there is no stage that only records violations
    • On the Free plan repository's settings screen, there was only one checkbox. From the moment you enable it, the applicable workflows fail

Rewrite to a full SHA, or use a lockfile?

There are two ways to get blocked workflows to pass: rewrite to a full SHA, or onboard with a lockfile. Based on this round of results, I organized the differences in my own way.

Aspect Rewrite to a full SHA Onboard with a lockfile
Tag references inside a composite action Cannot fix ones you don't manage yourself Recorded in the lockfile, and passed (what I tested was my own composite action)
Appearance of the workflow SHAs line up (leave the tag as a comment) Stays as tags
Tools used pinact, Renovate, etc. gh actions-lock (still 0.x)
When adding a workflow Run it through the rewriting tool Onboard it yourself

Regarding the handling of Dependabot, Locking action dependencies says, "Dependabot only updates lockfile entries for workflows that are already onboarded." Onboarding new workflows is not done automatically.

The lockfile became GA on October 28, 2026, and the Require lockfile policy that makes lockfiles mandatory is also included in the GA. If you are starting now, it seems good to center on the lockfile and combine it with SHA pinning enforcement. The documentation says that the lockfile pins even transitive dependencies by commit, which covers the inside of composite actions you don't manage yourself. Combined with enforcement, workflows with tag references that were forgotten to be onboarded are also stopped. Once all workflows have been onboarded, switching to the Require lockfile policy is also conceivable (I did not test it this time).

On the other hand, gh actions-lock is still 0.x, and its behavior changed in v0.3.0. For repositories that already operate pinning to full SHAs with pinact or Renovate, I think it is reasonable to continue that while moving to the lockfile one workflow at a time.

Summary

When you enable "Require actions to be pinned to a full-length commit SHA", actions referenced by tag, branch, or short SHA are stopped at the job preparation stage. Even when tag references were inside composite actions or reusable workflows, they failed with the same error. On the other hand, actions in the same repository, calls to reusable workflows, and docker:// steps were out of scope.

Workflows onboarded with a lockfile satisfied the enforcement requirement even inside the composite action while keeping tags. Lockfiles created with v0.2.0 needed to be recreated, but now that the lockfile is GA, I plan to choose the approach of centering on the lockfile and combining it with enforcement.

I hope this is helpful to someone.


Reference links:

Share this article