Secure Use of Github Actions - magma/magma Wiki

Github Actions allow third parties to access CI secrets, modify source code, and modify artifacts. Take care to limit security exposure.

Before working with Actions, review Good security practices for using GitHub Actions features.

When adding a new Action:

  • Investigate whether the vendor is trustworthy enough for privileged access
  • Select a particular tagged version, such as 1.2.3
  • Investigate the security risks of that version
    • Has it been in use for long enough to uncover vulnerabilities?
    • Are there security advisories?
    • Scan the source code, if that is a reasonable amount of work
    • Prefer Vigilant Mode

When updating the version of an Action:

  • Ask whether the need outweighs the security risk. Is there a specific new feature you need or a relevant security patch?
  • Select a particular version
  • Investigate the security risks of that version

You should almost always specify Actions in a workflow using the hash of a particular release rather than a version. Otherwise malicious updates can be injected without changing the version.

For example, the very-widely used action "checkout" is at, with releases at and a given release at a URL like The tag page will show the hash, like

Once you have the hash, add it to the YAML file in /magma/.github/workflows using syntax similar to:

- uses: actions/[email protected] # [email protected]

Compare that to the style using only a version number:

- uses: actions/[email protected]

Take care to include the trailing comment specifying the version and to have exactly the right number of spaces. This both improves readability and enables the pin-github-action tool to auto-upgrade.

To reduce future inconvenience, I am working with the maintainer of pin-github-action on a CLI to do it all in one shot.

Why Pin Actions By Hash

Best Practice

Action pinning has consensus wisdom behind it.

  • Github recommends it:

    Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release. Pinning to a particular SHA helps mitigate the risk of a bad actor adding a backdoor to the action's repository, as they would need to generate a SHA-1 collision for a valid Git object payload.

  • OpenSSF recommends it. It is checked in the OpenSSF Scorecard, which is how Magma came to embrace the practice.

    Pinned dependencies reduce several security risks:

    • They ensure that checking and deployment are all done with the same software, reducing deployment risks, simplifying debugging, and enabling reproducibility.
    • They can help mitigate compromised dependencies from undermining the security of the project (in the case where you've evaluated the pinned dependency, you are confident it's not compromised, and a later version is released that is compromised).
    • They are one way to counter dependency confusion (aka substitution) attacks, in which an application uses multiple feeds to acquire software packages (a "hybrid configuration"), and attackers fool the user into using a malicious package via a feed that was not expected for that package.


A reason not to do pinning is that we don't automatically get the benefit of upgrades to an action. However we have already adopted pinning in the form of go.sum and packages.lock, which come with the same disadvantage. If this disadvantage is compelling for Actions, we should reconsider pinning our Go and Javascript.

The ideal approach would be for us to automatically accept upgrades after they have been validated by a third party. Unfortunately there is no third party offering this service, whether for Actions or other package types.

Manual Hash Updates

A argument not to do pining is that it it is hard to do a manual upgrade. A developer who wants to upgrade the version of an Action can't use a CLI tool such as yarn upgrade [email protected].

Instead, a developer will either have to look up the hash of the version in the repo for the action, or use the pin-github-action tool to look up the version.

This is a drawback. I am working on tooling to simplify the work.

The counterargument is that this is not an excessive burden when addding a single action.

Lack of Verification

One reason not to do pinning is that we are blindly accepting all the versions currently in place. Replacing semver strings with hashes does nothing to vet the actions.

This is a one-time cost. There are too many unvetted actions at this point, about 50, but in the future when we are adding actions one at a time that will not be true.

This weakness also affected other lock file mechanisms in the past, such as packages.lock and go.sum.

No Alternative Mitigation

The assets we are protecting are secrets in the CI and built artifacts. By protecting them we prevent artifacts from being used for privilege escalation where artifacts are deployed.

There are many threat actors who could do this. We use quite a few actions. Some are very credible, but others are by small and relatively unknown providers.

How We Did It

To do the initial work of converting every version to a hash, I used pin-github-action. You shouldn't need to do that in the future but this utility may well continue to be useful.