GitHub Actions makes it easy to automate builds, run tests, publish packages, and deploy applications. A workflow can use existing actions to check out source code, configure a development environment, scan dependencies, and deploy an application without requiring developers to write every step themselves.
However, every external action introduces a dependency into the automation process. If a workflow references an action using a movable version tag, the code executed by that workflow may change without a corresponding change to your repository.
For example, a workflow that references an action using a major version tag such as v4 may receive a different implementation when the tag is updated. This is convenient for maintenance, but it makes the exact code executed during a workflow run less explicit.
Pinning actions to reviewed commit SHAs helps address this problem. It allows a team to identify the exact revision of an action that should run and makes unexpected changes easier to detect during code review.
This article explains how to pin GitHub Actions to full commit SHAs, manage these references consistently, review updates safely, and implement a practical workflow security process.
Why GitHub Actions References Need Protection
A GitHub Actions workflow is executable code. It can access repository contents, request permissions through the GitHub token, download dependencies, publish artifacts, and sometimes deploy directly to production.
If an action is compromised or unexpectedly changed, the consequences may extend beyond a failed build.
Depending on the workflow's permissions and configuration, a malicious action could attempt to access sensitive data, modify repository contents, or interfere with a deployment.
Understanding Action References
Consider this workflow step:
steps:
- name: Check out repository
uses: actions/checkout@v4
The reference uses a version tag rather than a specific commit SHA.
This approach is common because the tag is readable and convenient. However, a tag can be moved to another commit by someone with sufficient repository permissions.
A full commit SHA identifies a specific Git commit.
For example, an action reference has this general structure:
steps:
- name: Check out repository
uses: actions/checkout@<FULL_40_CHARACTER_COMMIT_SHA>
The placeholder is illustrative and is not a valid action reference. Replace it with the actual, verified 40-character commit SHA for the intended release.
Pinning to a commit SHA makes the reference explicit. If the action maintainer publishes a new release, the workflow will continue using the pinned commit until someone reviews and updates the reference.
What a Commit SHA Does and Does Not Guarantee
Pinning an action to a commit SHA improves reproducibility and reduces the risk of an action tag being moved unexpectedly.
However, it does not prove that the action is safe.
The pinned commit could already contain malicious or vulnerable code. An action may also download additional dependencies at runtime or execute scripts from other sources.
Therefore, pinning should be combined with code review, trusted action selection, restricted permissions, and periodic updates.
Pin GitHub Actions to Full Commit SHAs
The first step is to identify every action used by the repository and replace movable references with verified commit SHAs.
Find Actions Used in Your Workflows
GitHub Actions workflows are generally stored in the .github/workflows directory.
For example, a repository might contain the following workflow:
name: Build and Test
on:
pull_request:
push:
branches:
- main
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Restore dependencies
run: dotnet restore
- name: Build application
run: dotnet build --configuration Release
- name: Run tests
run: dotnet test --configuration Release
This example uses version tags for the checkout and .NET setup actions.
Before changing these references, identify the exact releases the repository intends to use. Review the action's official repository, release information, and commit history. Verify that the selected SHA belongs to the expected action and release.
Do not copy a commit SHA from an unrelated repository or assume that a SHA is trustworthy merely because it appears in a code example.
Replace Tags With Verified SHAs
After verifying the intended commits, update the workflow references.
The following example demonstrates the required structure. The placeholders must be replaced with verified commit SHAs before the workflow can run.
name: Build and Test
on:
pull_request:
push:
branches:
- main
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@<VERIFIED_CHECKOUT_SHA>
- name: Set up .NET
uses: actions/setup-dotnet@<VERIFIED_SETUP_DOTNET_SHA>
with:
dotnet-version: '10.0.x'
- name: Restore dependencies
run: dotnet restore
- name: Build application
run: dotnet build --configuration Release
- name: Run tests
run: dotnet test --configuration Release
The angle-bracket values above are explanatory placeholders, not valid GitHub Actions references. Replace each placeholder with the corresponding full commit SHA.
The action references now identify specific revisions instead of depending on movable release tags.
Keep the action versions and .NET SDK version appropriate for the application. Pinning an outdated action without an update process can preserve known vulnerabilities, so exact references must still be maintained.
Why Reviewed Commits Matter
A commit SHA provides a stable reference to a particular revision, but the security benefit depends on the review process used to select that revision.
Review the Action's Source Code
Before introducing or updating an action, examine its repository and intended release.
Look for changes involving:
Network requests to external services.
Access to environment variables and credentials.
Shell commands executed during the action.
File modifications outside the expected working directory.
Changes to token permissions or authentication behavior.
Additional dependencies downloaded or executed at runtime.
Unexpected changes to deployment or artifact publishing logic.
The amount of review required depends on the action's privileges and purpose.
An action that formats code has a different risk profile from an action that publishes packages or deploys production infrastructure.
Verify the Commit SHA
A full-length SHA should be obtained from the trusted action repository and verified against the intended release.
For example, Git can display a commit's full identifier:
git rev-parse <COMMIT_REFERENCE>^{commit}
Replace <COMMIT_REFERENCE> with a verified commit reference available in the local repository.
The command resolves the reference to a commit object and prints its full identifier. However, it does not establish that the commit is safe or that it belongs to the intended upstream release.
For third-party actions, verify the SHA against the action maintainer's repository and release information. Review the actual source changes before approving the update.
Check the Difference Between Releases
When upgrading an action, compare the current pinned commit with the proposed commit.
Review the changed files, dependencies, scripts, and release notes where available.
For high-privilege actions, consider requiring a second reviewer. This reduces the likelihood that an unexpected or malicious change enters the workflow through a routine dependency update.
What Is a GitHub Actions Workflow Lockfile?
A workflow lockfile is a mechanism for recording exact action revisions used by a workflow. The term can refer to different tools and formats, so it is important to distinguish a generated lockfile from GitHub Actions' native workflow syntax.
GitHub Actions supports referencing actions by commit SHA directly in workflow files. It does not require a universal, built-in workflow lockfile for this purpose.
Some teams use automation tools to update action references, generate lockfile-style metadata, or enforce SHA pinning as part of repository checks.
These tools can help maintain consistency, but their behavior depends on the tool and configuration selected.
Direct SHA Pinning Versus Automated Lockfile Management
Approach | How it works | Main benefit | Main limitation |
|---|
Version tags | References actions using release tags | Easy to read and maintain | Tags may move |
Full commit SHAs | References an exact commit | Explicit and stable revision selection | Updates require a reviewed reference change |
Automated SHA updates | A tool proposes updated commit references | Reduces manual maintenance | Proposed updates still require review |
Lockfile-style management | Records or manages resolved action revisions through an additional tool | Helps enforce consistent dependency management | Depends on the selected tool and configuration |
The best approach depends on the repository's security requirements and maintenance practices.
For security-sensitive repositories, direct SHA pinning combined with automated update proposals and review checks provides a practical baseline.
Automate SHA Pinning Checks
Manual reviews can miss an action reference, especially when a repository contains many workflows.
Automated checks can help identify references that do not follow the team's policy.
For example, a simple script can search workflow files for common version-tag references.
from pathlib import Path
import re
workflow_directory = Path(".github/workflows")
uses_pattern = re.compile(
r"^\s*uses:\s*([^\s#]+)",
re.MULTILINE
)
for workflow in workflow_directory.glob("*.yml"):
content = workflow.read_text(encoding="utf-8")
for match in uses_pattern.finditer(content):
action_reference = match.group(1)
if "@" not in action_reference:
continue
action_name, reference = action_reference.rsplit("@", 1)
if re.fullmatch(r"[0-9a-fA-F]{40}", reference):
print(f"PINNED: {workflow}: {action_name}")
else:
print(
f"REVIEW: {workflow}: "
f"{action_name}@{reference}"
)
This script is a basic inspection aid. It identifies references that look like full commit SHAs and flags other references for review.
It does not validate the SHA against the upstream repository, inspect action code, or guarantee that every possible workflow syntax is handled correctly. A production policy should use a maintained workflow parser or a dedicated action-pinning enforcement tool where appropriate.
The script also checks only files with the .yml extension. If your repository uses .yaml files, extend the file discovery logic to include them.
Enforce the Policy During Pull Requests
After establishing the policy, run the check automatically during pull request validation.
The repository can require the validation job to pass before changes are merged.
A useful policy should clearly define:
Whether every external action must be pinned to a full SHA.
Which actions, if any, are explicitly exempt.
How approved action updates are reviewed.
Who can approve changes to workflow security settings.
How urgent security updates are handled.
How the policy applies to reusable workflows and nested dependencies.
Avoid relying exclusively on a regular expression. A script can miss unusual syntax or incorrectly classify an action reference. Combine automated checks with a review process that understands the repository's workflow structure.
Keep Action Updates Secure and Manageable
Pinning actions is not a one-time task. Action maintainers publish security fixes, support new runtime versions, and change their implementation over time.
A pinned action will not automatically move to a newer release, which is useful for controlling changes but creates a maintenance responsibility.
Establish an Update Schedule
Review pinned actions regularly and when relevant security advisories are published.
For each proposed update:
Identify the currently pinned commit and the proposed replacement.
Verify the new commit against the trusted upstream repository.
Review the changes and relevant release information.
Run workflow validation and application tests.
Merge the update through the normal review process.
High-risk actions should receive more attention than low-privilege utility actions.
Avoid Unnecessary Permission Grants
A pinned action can still misuse the permissions available to it.
Set workflow-level permissions to the minimum required and grant additional permissions only to jobs that need them.
For example:
permissions:
contents: read
This grants read access to repository contents through the workflow's GitHub token. It does not automatically provide every permission needed by all actions.
If a job must publish a package, create a release, or deploy an application, grant the specific additional permissions required for that task at the narrowest practical scope.
Do not grant write access to the entire workflow merely to make one step succeed.
Protect Secrets From Untrusted Code
Review how credentials are passed to workflows and actions.
Avoid exposing deployment credentials to workflows that execute untrusted pull request code. Use appropriate environment protections, narrowly scoped credentials, and approval requirements for sensitive deployments.
Also remember that pinning an action does not pin every dependency it may download or execute. Review the action's runtime behavior and the other components in its supply chain.
Common Mistakes to Avoid
Using a Short Commit SHA
A short SHA is less explicit than the full 40-character commit identifier expected for GitHub Actions security pinning.
Use the complete SHA and verify that it identifies the intended commit.
Assuming a Pinned Action Cannot Be Compromised
A pinned reference prevents unexpected changes caused by moving the reference, but it does not make the selected code trustworthy.
Review the source and keep the reference updated when security fixes become available.
Updating Every Action Without Testing
A newer action version may change inputs, runtime requirements, or behavior.
Update actions through reviewed pull requests and run the workflows that depend on them.
Ignoring Reusable Workflows
Repositories can use reusable workflows as well as individual actions. Review references to reusable workflows and their dependencies according to the same supply-chain security principles.
Forgetting About Runtime Dependencies
An action may execute scripts or download additional components after it starts. Pinning the action's main repository commit does not necessarily make every runtime dependency immutable.
Review how these dependencies are selected and whether their integrity can be verified.
Best Practices for Production Repositories
A reliable GitHub Actions security policy should include the following practices:
Pin external actions to verified full commit SHAs.
Use trusted upstream repositories and review the intended release.
Require pull request reviews for changes to workflow files.
Automate checks that detect unpinned action references.
Update pinned actions regularly and respond promptly to security advisories.
Use minimum required workflow and job permissions.
Keep secrets away from untrusted code execution.
Review reusable workflows and dependencies downloaded at runtime.
Test workflow changes before relying on them for production deployments.
Document exceptions and assign responsibility for maintaining pinned references.
These controls reduce the risk of unexpected workflow changes while keeping the update process manageable.
Advantages and Limitations of Commit Pinning
Commit pinning provides a clear record of the action revision selected by the repository. It makes updates visible in code review and reduces the risk of a workflow silently following a moved tag.
It also improves consistency between workflow runs because the action reference remains tied to a particular commit.
However, pinning introduces maintenance work. Security fixes do not automatically enter the workflow, and a pinned revision may become outdated. Teams must monitor releases, evaluate updates, and replace references when appropriate.
Commit pinning should therefore be treated as one component of software supply-chain security rather than a complete security solution.
Summary
GitHub Actions workflows execute code that may have access to repository contents, tokens, secrets, and deployment systems. Referencing actions through movable version tags can make it harder to determine exactly which code a workflow will execute.
Pinning actions to verified full commit SHAs makes those references explicit and helps ensure that workflow updates pass through code review.
A practical implementation combines SHA pinning with automated policy checks, restricted permissions, protected secrets, and regular dependency maintenance. Teams should also review reusable workflows and dependencies that actions download at runtime.
The objective is to make workflow changes deliberate and traceable without allowing pinned dependencies to become permanently outdated. A secure process verifies the code before adoption and continues reviewing it throughout its lifecycle.
Join the conversation! Your thoughts help the community grow.