Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ draft: false # Set to false when ready to publish
title: 'Securing GitHub Actions Workflows'
publishDate: 2024-08-16
params:
authors: [{ name: 'Greg Mohler', handle: 'callmegreg' }, { name: 'Kitty Chiu', handle: 'kittychiu' }]
authors: [{ name: 'Greg Mohler', handle: 'callmegreg' }, { name: 'Kitty Chiu', handle: 'kittychiu' }, { name: 'Thomas Sjögren', handle: 'konstruktoid' }]

# Classifications of the framework to drive key concepts, design principles, and architectural best practices
pillars:
Expand Down Expand Up @@ -87,9 +87,10 @@ To secure GitHub Actions workflows, consider the following strategies:
7. **Avoid workflow injection**: Sanitize user input and avoid using expression values in sensitive contexts (such as `run` steps) to prevent injection attacks.
8. **Avoid `pull_request_target`**: This event runs workflows in the base repository context with elevated permissions. This can enable malicious execution using pull requests from forks.
9. **Secure `workflow_run` workflows**: Treat all artifacts, code, and data from triggering workflows as untrusted. Use [branch filters](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#limiting-your-workflow-to-run-based-on-branches) and validate all inputs.
10. **Use `head.sha` instead of `head.ref`**: Where possible, reference by commit SHA instead of a user-provided branch name or tag (ref), especially in sensitive contexts (such as `run` steps). If require, use environment variable to store `head.ref` and reference it to prevent injection attack.
10. **Use `head.sha` instead of `head.ref`**: Where possible, reference by commit SHA instead of a user-provided branch name or tag (ref), especially in sensitive contexts (such as `run` steps). If required, use environment variable to store `head.ref` and reference it to prevent injection attack.
11. **Use caution with public repositories**: Anyone can suggest changes to public repositories. Review workflow triggers, and never use self-hosted runners with public repositories.
12. **Restrict allowed actions**: Use the [*Allow enterprise, and select non-enterprise, actions and reusable workflows*](https://docs.github.com/en/enterprise-cloud@latest/admin/enforcing-policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-github-actions-in-your-enterprise#controlling-access-to-public-actions-and-reusable-workflows) setting to control which actions can run.
13. **Segregate runners**: Use runner groups and labels to separate high-privilege runners from low-privilege runners, and restrict high-privilege runner groups to selected repositories or workflows to reduce exposure to sensitive resources.

## Assumptions and preconditions

Expand Down Expand Up @@ -126,6 +127,7 @@ Repository rulesets provide a strong defensive layer that complements workflow-l
- [Require status checks to pass before merging](https://docs.github.com/en/enterprise-cloud@latest/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets#require-status-checks-to-pass-before-merging): Ensure automated validation checks pass before merging.
- [Require code scanning results](https://docs.github.com/en/enterprise-cloud@latest/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets#require-code-scanning-results): Identify security vulnerabilities before merge.
- [Require signed commits](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets#require-signed-commits): Ensure all commits are signed to prove who authored them and that they haven't been modified.
- [Require workflows to pass before merging](https://docs.github.com/en/enterprise-cloud@latest/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets#require-workflows-to-pass-before-merging): Ensure organizational or enterprise-level requirements for workflows are met before merging. This could be a workflow that checks for required labels, validates commit messages, or performs other organizational policy checks.
- Restrict bypass permissions: Limit bypass capabilities to emergencies and monitor via audit logs.

### Implement least privilege for workflow permissions
Expand Down Expand Up @@ -257,6 +259,18 @@ The [allowed actions and reusable workflows setting](https://docs.github.com/en/

Consider defining the list of allowed actions using policy as code (e.g., via Terraform or the REST API) to establish a request/approval process, track changes for audit purposes, and improve visibility into which actions are allowed.

### Segregate runners

Use [runner groups](https://docs.github.com/en/actions/concepts/runners/runner-groups) or [labels](https://docs.github.com/en/actions/how-tos/manage-runners/self-hosted-runners/apply-labels) to separate high-privilege runners from low-privilege runners. High-privilege runners may have access to sensitive resources, while low-privilege runners should not.

This separation provides more granular control over [which repositories can access different runners](https://docs.github.com/en/actions/how-tos/manage-runners/self-hosted-runners/manage-access#changing-which-repositories-can-access-a-runner-group) and which [jobs can access specific runners](https://docs.github.com/en/actions/how-tos/write-workflows/choose-where-workflows-run/choose-the-runner-for-a-job). It also reduces the risk that a compromised or misconfigured workflow could gain access to sensitive resources.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clarify the "which jobs can access specific runners" ? this cannot be enforced once a repo has access to a given runner (you can restrict which workflows can use the runner but not the jobs) any job can use it.

Unless you restrict the workflows that can access a runner if a user can author workflows in a repo which has access to the runner it can use it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it's actually workflows but https://docs.github.com/en/actions/how-tos/write-workflows/choose-where-workflows-run/choose-the-runner-for-a-job#choosing-self-hosted-runners says "To specify a self-hosted runner for your job, configure runs-on in your workflow file with self-hosted runner labels." and "When you combine groups and labels, the runner must meet both requirements to be eligible to run the job." (https://docs.github.com/en/actions/how-tos/manage-runners/self-hosted-runners/use-in-a-workflow#using-labels-and-groups-to-route-jobs).


For example, you could create:

- A runner group for container image build runners, limited to only the repositories that require those privileges.
- A runner group for runners with access to restricted networks.
- A separate runner group for low-privilege tasks such as linting and static analysis, used in repositories where secrets are either absent entirely or isolated in separate environments.

## Additional solution detail and trade-offs to consider

### Pinning actions based on a version tag
Expand Down