Pin GitHub Actions

GitHub recommends to pin an Action to a full length commit SHA as it is currently the only way to use an Action as an immutable release.

Still, only 2% of GitHub repositories fully embrace this security best practice!

The data was collected May 2024 and shows the top 10,000 GitHub repositories (by stars). ~50% of repositories aren't using Actions, and are excluded from the chart.

Download the raw data (~140kb tar.gz), if you want to analyze it further! The code to generate this data is available on GitHub, please report any issue.

Why you should pin your GitHub Actions?

Most GitHub Action pipelines are composed of 3rd party Actions. To use one, point to a git repository that hosts this Action and reference a version, such as a branch (@main) or a tag (@v1).

                
  jobs:
    build:
      steps:
        - name: Check out repository
          uses: actions/checkout@v4
            

This references the v4 tag in the actions/checkout GitHub repository.

Referencing a branch is a mutable reference, meaning that the Action will change as the branch is updated.

A less known fact is that a tag is also a mutable reference. The maintainer of a repository (or a malicious attacker) can delete a tag and create a new one with the same name, effectively changing the Action that is being used. This mechanism is used by the Actions/checkout maintainers to always point the @v4 tag to the latest version of the action.

To avoid this, reference a specific commit hash, an immutable reference. The action will always be the same, even if the tag is deleted.

                
  jobs:
    build:
      steps:
        - name: Check out repository
          uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
            

How to secure (and update) your GitHub Actions?

Neither managing hashes nor manually updating Actions is fun or developer-friendly. We take the pain out of this process with tools and automations available to the community.

1. Migrate with Frizbee

Instead of manually looking up the digest for each tag or branch in our GitHub Actions, we can use Frizbee. Frizbee is a tool you may throw a tag at and it comes back with a checksum.

Download the latest release and point Frizbee at your GitHub Actions folder:

frizbee ghactions -d path/to/your/repo/.github/workflows/

This will replace all tags and branches with their corresponding digest, freezing your GitHub Action dependencies in time.

2. Update pinned Actions with Renovate

Even when pinning our GitHub Actions, we still want to receive updates, but we are in control when to apply those. Renovate has build in support to update GitHub Actions that are pinned. It even understands SemVer ranges and keeps the version comment up to date, so a developer can easily keep track of the used versions.

Simply add these setting to your renovate.json:


    {
        "$schema": "https://docs.renovatebot.com/renovate-schema.json",
        "extends": [
          "config:base",
          ":preserveSemverRanges",
          "helpers:pinGitHubActionDigests"
        ],
        // rest of your config
     }
            

Known Limitations

Pinning GitHub Actions will not protect us from all attacks. It is important to understand which risks remain.

A blog post by PaloAlto Networks Unpinnable Actions: How Malicious Code Can Sneak into Your GitHub Actions Workflows demonstrates that dependencies can still introduce breaking changes or have the potential to introduce malicious code into our pipelines.