Github Actions Security Best Practices
By Reethi Kotti
In the world of Continuous Integration and Continuous Deployment, Github Actions provide a nifty edge to quickly build end-to-end automation right into the repository.
This makes integration of Actions into an organization’s Github repositories pretty straightforward and convenient.
Github Actions bring velocity to the Software Development Lifecycle. However, if it is swiftly adopted without a well chartered security plan, you may quickly find yourself in muddy waters.
In this blog post, we will discuss some of the key security concerns you should be aware of when using Github Actions. We will also cover the best practices that Salesforce Heroku follows to securely use this exceedingly popular product.
A little about Github Actions
Github Actions enables users to run workflows, which are custom automated processes that can be set up in a repository to build, test, package, release, or deploy any code project. These workflows can be executed on Github runners or self-hosted runners.
A workflow file constitutes one or more jobs, and each job is broken down into steps. A step can either execute commands on the runners or utilize an Action to perform a certain task. When building workflows, engineers can write their own custom Actions or utilize the available Actions in the Github Actions marketplace.
The following diagram shows the various components of a workflow file:
To understand how a workflow is broken down into jobs and steps, let’s look at a specific scenario.
For every push to a repository, code should be scanned for open source vulnerabilities using Snyk and files should be uploaded to an S3 bucket
The workflow file for this task may look something like this:
A couple of things to note:
- This workflow will be executed when a push event occurs on any branch of the repository (line 2).
- The workflow has been broken down into two jobs, as they are distinct tasks that do not depend on each other. They will run simultaneously on two isolated containers.
- Job “snyk_run” (line 4) is divided into two steps that run on the same container in the defined order. This permits the steps within a job to share information via the filesystem.
- The Snyk action used here (line 10) requires a sensitive token that is stored via Github Secrets.
- The last step in “upload-files” (line 32) runs a command to execute a script defined in the repository.
Additional information about Github Actions can be found at https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions
What can go wrong?
Github Actions is an attractive solution to automate tasks and run tests. Integrating Actions into Github repositories, however, can add to an organization’s risk surface. A few areas of concern are noted in the table below:
- Third party actions: The third party Action used could potentially run malicious code.
- Secrets and other sensitive information: Actions may need access to secrets — these need to be stored securely and referenced safely.
- By-products of workflow runs: Access to by-products of a workflow execution (artifacts, caches) should be audited.
- Forked repositories: Access to logs and/or secrets through forked repositories should be examined.
- Malicious docker images: Actions could be built on malicious docker images.
- Service Containers: Use of default credentials and access to service containers (redis, postgres) should be audited.
- Runner escape: Malicious code on runners could lead to a potential container breakout.
- Workflow commands: Commands used in workflows may not function as expected/documented.
- Improper configuration of self-hosted runners: Attacks on runners ( remote code execution, privilege escalation) via Actions can lead to lateral movement in an organization’s environment.
- Third party product vulnerabilities in Actions: Packages used by an Action could have multiple open source vulnerabilities.
As noted above, things can go wrong if Github Actions has been hastily adopted. The best practices we’ll discuss in the following sections help address these concerns.
RECOMMENDED THIRD PARTY ACTIONS TO USE
To minimize risk associated with third party Actions, a standard/guidance should be developed to define “trusted” actions. Based on your particular needs and risk appetite the criteria *may* include:
- GitHub provided standard actions,
— These are developed and maintained by GitHub.
— A list of these actions can be found at https://github.com/actions
- Actions whose creators have been verified by GitHub,
— The blue badge next to the action identifies verified creators.
- Actions that have been released by trusted vendors of your organization.
For Actions falling outside this criteria, it is highly recommended that a security assessment or code review should be conducted before use.
WRITING SECURE WORKFLOWS
Poor practices while writing workflows may quickly compound risk and introduce security gaps. Below are a list of best practices that can help minimize these threats:
- Use a stable version tag when calling Actions in a workflow file,
— Example: actions/checkout@v2
— Alternatively, the commit hash of the stable version can be used, actions/download-artifact@1de1dea89c32dcb1f37183c96fe85cfe067b682a
- Do not use @master tag while calling an Action,
— Example, *this is NOT recommended*: actions/upload-artifact@master
- Use only official or docker certified images when running jobs on docker containers.
— An appropriate security review should be obtained if an image not falling in the aforementioned category is needed.
- When pulling docker images, use the version tag (preferably with the major version),
— Example: node:10
—version tag is preferred over node:latest.
- Do not use the add::mask command to prevent sensitive values from appearing as plaintext in workflow logs.
— If used with environment variables, plaintext values are printed as part of the execution environment into the logs.
- Do not overwrite Github default variables, these have the prefix GITHUB_.
- Do not store sensitive values i.e. access tokens in a file in the cache path.
— Do not include <home>/.docker/config.json file in the cache, this file may contain unencrypted docker credentials.
- To create the unique key that identifies a cache, using context data is a common practice
— However, do not use sensitive values like github.token or github.actor for key generation.
— Instead use a hash of a file like requirements.txt or package-lock.json in combination with system related information like runner.os.
- Do not dump github context data into logs as it might contain sensitive information
- Artifacts can be created for sharing information between jobs in the same workflow.
— Do not store sensitive information in artifacts as they are available to anyone with access to the repository.
— Artifacts are automatically deleted after 90 days. Safely upload them into an s3 bucket if needed beyond that time period.
- Be aware of job execution limits.
- Maintain caution when adding outside collaborators — users with read permissions can view logs for workflow failures, view workflow history, as well as search and download logs.
—External collaborators with write access to a repository can modify permissions associated with a GITHUB_TOKEN. This can lead to elevated privileges and possibility for other attacks 
— If adding external collaborators is required, follow the principle of least privilege and assign only read permissions for minimal disclosure.
USING GITHUB SECRETS THE RIGHT WAY
Sensitive data like access tokens or credentials may be needed during workflow runs. Github Secrets is an built-in secret storage mechanism that can and should be used for these purposes. However, to ensure secrets are safely referenced, here is a list of recommendations to follow:
- Store all credentials and anything considered sensitive using Github Secrets.
- Github redacts values defined via Secrets from logs; however, do not print secrets to logs deliberately.
- If a Secret value is greater than 64kb, then users can follow the process defined in https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#limits-for-secrets
— Secrets stored this way are not automatically redacted by GitHub.
— Do not print secrets to logs.
— Do not use secrets directly in command line programs.
- In the workflow file, use secrets as environment or normal variables.
— Use quoting when passing them on the command line.
- When a secret is being shared between multiple repositories, they can be added at the Organization level if feasible.
— This avoids duplication and simplifies the rotation process.
- Avoid passing secrets between processes from the command line.
- Establish a credential rolling process and regular cadence.
Github provides useful settings that can be utilized at organization or repository level to further minimize risk associated with Actions. These have been described below
- To restrict actions used at the repo level:
— If use of third party actions is not required, in Settings -> Actions tab, enable “Allow local actions only”
— To use actions created by Github and Github verified creators, in Settings -> Actions tab, choose “Allow Select Actions”. Enable “Allow actions created by Github” and “Allow Marketplace actions by verified creators”
- Forks of a repository, however, do not inherit Secrets, but they can create pull requests on the base branch and access caches. To minimize any information disclosure, if forking is not needed, it can be disabled in the repository under Settings -> Options
- To prevent workflow runs from pull requests originating from repository forks, in Settings -> Actions tab, ensure “Fork pull request workflows” is disabled.
- To control access of a repository from workflows originating in other repositories, the “Access” setting can be utilized. This is available under Settings -> Actions tab.
RECOMMENDATIONS FOR CUSTOM ACTIONS
Engineers may want to build their own custom actions to automate tasks. Described below are a few recommendations to keep in mind while doing so:
- When creating docker container actions, use official or docker certified images.
— Use the version tag (preferably the major version if available).
- If the custom action is built to interact with internal applications or approved third party applications, store relevant credentials using Secrets.
- Create a separate repository for every action that is to be open sourced.
- https://help.github.com/en/actions/creating-actions/about-actions#good-practices-for-release-management lists good practices for creating a public action.
An organization may have Actions enabled in their public Github repositories. Described below are few recommendations to keep in mind with respect to Github Actions in public repositories:
- Github Actions is enabled by default in all repositories. Disable Actions in public repositories when not required
- In Settings -> Actions tab, for “Workflow permissions”, select “Read repository contents permission”. This ensures least privileges are given to the associated GITHUB_TOKEN
— If “Read and Write permissions” are assigned, within a workflow file, an user can add additional permissions to the GITHUB_TOKEN
— This setting can be enabled at the organization level
- To control workflow runs by outside collaborators:
— In Settings -> Actions tab, for “Fork pull request workflows from outside collaborators” select “Require approval for all outside collaborators”
Additional best practices can be found at https://docs.github.com/en/actions/learn-github-actions/security-hardening-for-github-actions
To sum up, although Github Actions comes with the promise of conveniently integrating CI/CD into code repositories, if left unchecked, like most any tool, it brings its own risks. However, incorporating these simple security recommendations enables users to approach Github Actions in a secure manner.