Stay Secure with GitHub Actions
Actions are awesome, they allow you to easily extend your GitHub Actions workflow with new functionality, without the need to reinvent the wheel.
The flexibility and offering of the marketplace with
3rd party actions is great, but sadly the wrong security of the marketplace has a high risk associated with it.
There's a huge amount of actions you can use to build the project, but only a small amount is from GitHub officially, or from verified sources. 3rd party actions should be reviewed so they are secure.GitHub Actions Introduction
But what is the risk of using potentially compromised or insecure actions?
- Source code being stolen / leaked
- Result artifacts being exposed
- Keys or credentials being stolen / exposed
- Code getting injected into the runner / resulting app
- Secrets being available to non trusted entities
GitHub offers different options to allow repo administrators to enforce strict rules on which actions can be used within a given repository.
Beside forcing only specific actions being allowed for builds, it's also possible to prevent workflows to be executed for PRs from forks and even more specifically to prevent fork pull request to retrieve write tokens or secrets.
verified actions can be considered to be ok (please be aware that it is never wrong to also review the code of these plugins, if a high risk system is involved).
The easiest step to ensure secrets are only available to trusted parts of your build, is by having them specified for the smallest scope possible. Prefer specifying secrets only to the steps they are needed for, instead of globally. The general rule of thumb for scoping secrets is:
step > job > workflow
One risk with pull requests from forks is, that 3rd party entities can fully modify the workflows and introduce risky actions or extract secrets from the repository. As such, if you can't trust all entities having access to the repository and to forks, do prevent these PRs from running workflows.
3rd party actions
Beside untrusted entities potentially getting access and extracting secrets from a repository, the biggest risk are
3rd party Actions which may contain insecure or malicious code.
The most secure would be to simply prevent any 3rd party action from being used and executed, which sadly impacts the flexibility and power of GitHub Actions. Resulting in significant overhead in having all actions in house.
If regardless of the potential risk involved with 3rd party action, you want to use them, make sure to follow these steps of reviewing the source:
- Review the code inside the Actions repository
- Verify the compiled code executed by the action
- Verify the
action.ymlexecuting the expected code
- (if docker action) Verify the
Dockerfileand the executed steps
- Use specific commits instead of versions
To showcase the above steps let's have a look at the
styfle/cancel-workflow-action action. (This action is one of my personal favourites as it will ensure to cancel jobs if they get to be obsolete by new pushed commits on a PR)
Review the code
The first step is to review the code. Luckily actions are required to be on GitHub as such we can directly review it's code:
Looking into it, no malicious sections can be found. The plugin uses the github core dependency to retrieve the workflows and cancel a running workflow if applicable.
Verify compiled code
Usually actions will also have their compiled source available in the
dist folder, this will be the piece which is ultimately executed by GitHub actions.
It's more difficult to check this, generally it helps to compile the action manually and then diff the resulting files to identify any suspicious sections.
The entry point for GitHub action to run the specified action is the
action.yml so it's important that we verify, it actually executing the source as highlighted above (and not do any other malicious steps)
As we can see, it will actually just execute the
runs: using: 'node12' main: 'dist/index.js'
The used example is a normal action, and does not consist out of a docker container. Just to showcase, here's a small example of an action using a docker container:
FROM python:3.6-slim ... COPY requirements.txt /action/ RUN pip install --upgrade --force --no-cache-dir pip && pip install --upgrade --force --no-cache-dir -r /action/requirements.txt COPY githubext /action/githubext COPY publish_unit_test_results.py /action/ ENTRYPOINT ["python", "/action/publish_unit_test_results.py"]
As this Dockerfile shows, it will use the
python container, and then copy various files into the container, and use
python to execute the
python file including the logic for that action
Use specific commit
After verifying that the action contains no malicious code and is considered save to be used, the last suggestion is to not use labeled versions to include the action in the workflow.
# dangerous firstname.lastname@example.org
0.5.0 references to the tag with the same name which is associated with a specific commit (we just verified to be secure).
The problem with this is, that tags can be changed, and associated with a different commit. This would give malicious parties the potential to modify the plugins source to execute malicious code, and then change the tag to point to this code, at any time. This is a high risk as even if we reviewed the action, it could at any point suddenly do something else.
To prevent this find the associated commit hash from the tag and specify the action using the hash in your workfow.
Now your builds will use this specific state of the action, and it's not possible anymore to modify that state, as every commit will result in a different hash.
GitHub actions can be dangerous if used without care, but with a few small steps you can protect your code and your project.
- Prevent external entities to get access to secrets
- Disallow 3rd party actions if feasible
- Limit the scope of secrets as close to where they are needed as possible
Got thoughts, feedback, improvements, suggestions, or comments?
Let me know @mike_penz