Protect the repository hosting your GitHub Action
Tags: githubactions, github, security, supplychainsecurity
Published: 2023-09-14 21:38:33

---
title: Protect the repository hosting your GitHub Action
published: true
date: 2023-09-14 21:17:53 UTC
tags: GitHubActions,GitHub,Security,SupplyChainSecurity
canonical_url: https://jessehouwing.net/protect-the-repository-hosting-your-github-action/
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o3ozbqo4yi1t15ier7by.jpg
---

Cover [photo used under Creative Commons](https://www.flickr.com/photos/jono2k5/504886535)

It comes as no surprise that the tags and branches solution to version GitHub Actions is weak at best. There have been rumors of Actions moving to a different model (GitHub Container Registry), but that is yet to see the light.

{% embed https://www.paloaltonetworks.com/blog/prisma-cloud/github-actions-worm-dependencies/?ref=jessehouwing.net %}

To protect your GitHub Actions repository there are a few things the author of an Action should do.

## 1. Setup 2FA

The [author of a GitHub Action should _always_ enable 2FA for their account](https://docs.github.com/en/authentication/securing-your-account-with-two-factor-authentication-2fa/configuring-two-factor-authentication?ref=jessehouwing.net).

You could consider putting all your actions in a [GitHub Organization instead of your personal account, which enables a number of extra policies (like enforced 2FA)](https://docs.github.com/en/organizations/keeping-your-organization-secure/managing-two-factor-authentication-for-your-organization/requiring-two-factor-authentication-in-your-organization). This will also allow you to enable the **✅ verified creator** on your marketplace listing.

## 2. Limit default permissions of your Actions in your repo

In the Actions settings you can limit what actions can do by default, requiring the author to set explicit permissions thus reducing the default permissions of your workflows.

Limit the actions the repo is permitted to run to the exact list of actions used by your repository:

![Protect the repository hosting your GitHub Action](https://jessehouwing.net/content/images/2023/09/image-5.png)

You can further limit what a GitHub Actions workflow can do. Recommended settings:

- **Require approval for all outside collaborator**
- **Read repository contents and package permissions**
- Do **not** allow **GitHub Actions to create and approve pull requests**

![Protect the repository hosting your GitHub Action](https://jessehouwing.net/content/images/2023/09/image.png)

## 3. Setup tag protection

[Setup protected tags to limit who can overwrite your tags](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/configuring-tag-protection-rules?ref=jessehouwing.net). This will prevent GitHub Actions workflows from overwriting existing tags. It won't prevent the creation of new minor versions though. Major versions will need to be updated by a repository owner or a GitHub App token with Repository Admin permissions.

> Unfortunately, the tag name format doesn't support regular expressions. To not block the creation of a tag called `verify-sanity` or any other tag name that starts with a `v`, I've added the protections for all the ways a version tag can be named in GitHub Actions.

Create a protected tag for `v0` to `v9` as well as `v*.*` and `v*.*.*`.

![Protect the repository hosting your GitHub Action](https://jessehouwing.net/content/images/2023/09/image-1.png)

## 4. Setup Branch protection

With the new Ruleset feature you can limit the branches that can be created. GitHub Actions will favor a branch with the name `v1` over a tag with the same name. Thus, a new branch could be used to redirect users of your action to a different implementation.

> Unfortunately, the branch format doesn't support regular expressions. To not block the creation of a branch called `verify-sanity` or any other branch name that starts with a `v`, I've added the protections for all the ways a version tag can be named in GitHub Actions.

Create a new Rule named "Do not allow versioned branches" and a rule for `v0` to `v9`:

![Protect the repository hosting your GitHub Action](https://jessehouwing.net/content/images/2023/09/image-2.png)

Configure the policy to:

- **Restrict creation**
- **Restrict updates**
- **Block force pushes**

![Protect the repository hosting your GitHub Action](https://jessehouwing.net/content/images/2023/09/image-3.png)

Also be sure to setup a Branch Protection for your main branch. Personally, I've setup the following rules:

![Protect the repository hosting your GitHub Action](https://jessehouwing.net/content/images/2023/09/image-9.png)

![Protect the repository hosting your GitHub Action](https://jessehouwing.net/content/images/2023/09/image-11.png)

If you have more than one maintainer, you block everyone from bypassing these rules. If you're a lone maintainer like me, you'll have to allow yourself to bypass this protection.

![Protect the repository hosting your GitHub Action](https://jessehouwing.net/content/images/2023/09/image-10.png)

## 5. Setup GitHub's security features

Enable Secret Scanning and push protection:

![Protect the repository hosting your GitHub Action](https://jessehouwing.net/content/images/2023/09/image-6.png)

Enable Dependabot Alerts and Version Updates:

![Protect the repository hosting your GitHub Action](https://jessehouwing.net/content/images/2023/09/image-7.png)

Add a `.github/dependabot.yml` to your Actions's repository:

```
version: 2
updates:
  - package-ecosystem: "github-actions" 
    directory: "/" # Location of package manifests
    schedule:
      interval: "weekly"
```

## 6. Use GitHub App Tokens or Fine-grained Access Tokens 

Now that the [Fine-grained personal access tokens support both the REST and the GraphQL API](https://github.blog/changelog/2023-04-27-graphql-improvements-for-fine-grained-pats-and-github-apps?ref=jessehouwing.net), be sure to delete any remaining Classic Personal Access Tokens and replace them with a Fine-grained token with only the scopes, organisations and repositories needed by the token.

Where possible limit the token to **Only select repositories** :

![Protect the repository hosting your GitHub Action](https://jessehouwing.net/content/images/2023/09/image-8.png)

If you are keeping all your actions in an organisation instead of your personal account and [have enabled Single Sign-on, then your Access tokens will require additional SSO-authorization](https://docs.github.com/en/enterprise-cloud@latest/authentication/authenticating-with-saml-single-sign-on/authorizing-a-personal-access-token-for-use-with-saml-single-sign-on). This will add yet another layer of security. [Single Sign-on is unfortunately only available for GitHub Enterprise Cloud, not on the Free and Teams plan](https://docs.github.com/en/enterprise-cloud@latest/admin/identity-and-access-management/using-saml-for-enterprise-iam/deciding-whether-to-configure-saml-for-your-enterprise-or-your-organizations).

In actions workflows [rely on GitHub Apps instead of personal acces tokens](https://github.com/marketplace/actions/github-app-token?ref=jessehouwing.net):

```
steps:
- id: create_token
  uses: tibdex/github-app-token@0914d50df753bbc42180d982a6550f195390069f # v2
  with:
    app_id: ${{ secrets.APP_ID }}
    private_key: ${{ secrets.PRIVATE_KEY }}

- run: "echo 'The created token is masked: ${{ steps.create_token.outputs.token }}'"
```

## 7. Prefer forks to collaborate over adding collaborators to the repo

This last item may be a bit paranoid, but GitHub protects your repository quite a bit better when other collaborators submit their changes from a fork instead of from a branch in the same repository.

This way collaborators can't:

- Create tags
- Create branches
- Access repository secrets
- Run a changed workflow without approval (see policy above)

## 8. Use `sha` over tags in all workflows used by your own GitHub Action.

Enable [RenovateBot](https://github.com/marketplace/renovate?ref=jessehouwing.net)or [OSSF Scorecard](https://github.com/marketplace/actions/ossf-scorecard-action?ref=jessehouwing.net) to automatically replace the version tags in your workflows and composite actions with their `sha`.

![Protect the repository hosting your GitHub Action](https://jessehouwing.net/content/images/2023/09/image-12.png)

[For RenovateBot add the following snippet to your `.github/renovate.json`](https://docs.renovatebot.com/modules/manager/github-actions/?ref=jessehouwing.net):

```
{
  "extends": ["helpers:pinGitHubActionDigests"]
}
```

And when these tools suggest upgrading to a newer version, make sure you verify the contents of the new release.

## 9. Reduce your dependencies​

I just went over all my own GitHub Actions and Azure Pipelines Task repositories and removed 3rd party dependencies that I didn't strictly need. For example, I had a dependency on `marvinpinto/action-automatic-releases@latest` which I replaced with 3 lines of code in a run block:

```
git tag latest HEAD --force
git push origin latest --force
gh release create latest --generate-notes --prerelease --latest --title "Development Build" ./ppt-diffmerge-tool/bin/Release/ppt-diffmerge.zip
```

One less dependency to worry about. This way I managed to remove most of my non-major-vendor dependencies.

## 10. Ensure your tags are versioned correctly​

GitHub Actions requires the author of the action to manage the versioning of the action itself. It does that through tags. One set of tags should be locked down, your build/patch versions (`v1.2.x`), the other tags are floating and should be updated every time a new build/patch version is added (`v1.x`, `vx`).

There is no built-in mechanism to ensure the versions have been updated correctly and if `v1` doesn't point to `v1.0.5`, but instead still points to `v1.0.0`, then all the people using `@v1` of your action will forever be on the older version.

To at least ensure your major version is pointing to the latest build/patch version, you can use an action I've created:Actions SemVer Checker - GitHub Marketplace Checks the version tags for your action repository to ensure the correct versions will be picked

[![Actions SemVer Checker Action](https://media.licdn.com/dms/image/sync/D4D27AQGyZ7834vbxGw/articleshare-shrink_800/0/1699918602478?e=1700830800&v=beta&t=HkTo6Oqnm4IUb6oACIlkbciDqKXL5WZa8qju89OvgoU)](https://github.com/marketplace/actions/actions-semver-checker)

Here's a sample workflow that will flag any versioning issues:

```yaml
name: Check SemVer

on:
  push:
    tags:
      - '*'
  workflow_dispatch:

jobs:
  check-semver:
    concurrency:
      group: '${{ github.workflow }}'
      cancel-in-progress: true
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: jessehouwing/actions-semver-checker@v1
        with:
          warn-minor-version: true
```

## Conclusion​
There are a lot of things an action author can do to ensure the security of their own actions. Apart from all of these repository settings, it's of course important to create an action that's secure by itself. The GitHub Security Lab Blog has written a number of articles on Action Security that I whole heartedly recommend:

 * [Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/)
 * [Keeping your GitHub Actions and workflows secure Part 2: Untrusted input](https://securitylab.github.com/research/github-actions-untrusted-input/)
 * [Keeping your GitHub Actions and workflows secure Part 3: How to trust your building blocks](https://securitylab.github.com/research/github-actions-building-blocks/)

Let's build a more secure Actions ecosystem together!