I’ve been diving into the security aspects of using GitHub Actions and wanted to share some best practices in one place. If you like to get an overview through a presentation setting instead of a blog, you can also find one of my conference sessions on it here.
All posts eventually lead up to setting up an internal marketplace for GitHub Actions: something that your team can control, prevents random actions from being used and gives you the process to run actual security checks on the actions in use. Learn how to set one up in your organization here.
In the post on Forking action repositories I show these best practices:
Additionally (and especially in an enterprise setting), you’ll want to create your own internal market place for actions. How to set it up and have a good security process around it can be found here.
After forking all the actions you want to use, you also have to own the maintenance. I’ve described a good way of keeping your forks up to date here, by making sure you review the incoming changes before you merge them.
In the post on Private runners I explain these best practices:
To have an additional boundary for your action you can run it inside a container. This also enables you to use something in your container that doesn’t have to be installed on the runner itself: Run your action in a container.
To mitigate a lot of attack vectors from running your runners on a virtual machine (e.g. disk / network access), you can host your self-hosted runners in a Kubernetes cluster. Then you have ‘ephemeral’ runners that only exist during the execution of your workflow and then are cleaned up.
An overview from GitHub on untrusted input, from the issue title to the commit message, if you act upon them (even just echoing them to the output!), they can be misused and therefor are an attack vector.
It used to be the case that you would trigger your workflow by using the pull_request
in the trigger definition. That scope had to much rights, so GitHub has dialed that down. The scope with more rights (to your access token with some write permissions for example) has now been created to be pull_request_target
. You need to be really careful with using that scope. Best practice here is to use a label for the pull request so you can manually check the PR and authorize its actual execution.
You need to be very careful with incoming Pull Requests from any fork: potentially they are changing scripts/commands in your workflow or even the workflow itself. There have been examples where a PR was send in that updated the workflow and made good use of the 10 concurrent jobs available for open source projects to generate some bitcoin for the attacker. This is why we can’t have nice things 😲!