Securing GitHub Actions Workflows
Scenario overview
Continuous integration and continuous delivery (CI/CD) are essential components of modern software development. GitHub Actions is a powerful tool that enables developers to automate repetitive tasks and reduce the risk of human error in manual workflows.
However, CI/CD tools inherently provide remote code execution as a service, making them a prime attack vector for malicious actors. Securing GitHub Actions workflows is critical to prevent unauthorized access to the codebase and protect organizations from potential security breaches.
Key design strategies
To secure GitHub Actions workflows, consider the following strategies:
- Use OpenID Connect (OIDC) for authentication: Establish authentication between GitHub Actions and external systems or cloud providers using OIDC. Use short-lived tokens whenever possible to access resources, and avoid storing long-lived credentials as secrets.
- Configure repository rules: Implement repository rulesets to enforce security policies and prevent unauthorized or malicious changes.
- Implement least privilege for workflow permissions: Restrict workflow and job permissions to the minimum required. Remove workflow-level permissions and define permissions on individual jobs rather than at the workflow level for finer-grained control.
- Use Dependabot to protect the supply chain: Enable Dependabot alerts and security updates to identify and remediate vulnerable dependencies in workflows. Enable version updates to regularly review and update dependencies.
- Pin versions of actions: Pin actions to a specific commit hash to prevent breaking changes or vulnerabilities in newer versions. Enforce via the allowed actions and reusable workflows policy at the enterprise, organization, or repository level.
- Avoid actions with mutable dependencies: Do not use actions that include mutable dependencies (e.g.
latest) or pull in external binaries without verification, as they introduce supply chain risks. - Avoid workflow injection: Sanitize user input and avoid using expression values in sensitive contexts (such as
runsteps) to prevent injection attacks. - 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. - Secure
workflow_runworkflows: Treat all artifacts, code, and data from triggering workflows as untrusted. Use branch filters and validate all inputs. - Use
head.shainstead ofhead.ref: Where possible, reference by commit SHA instead of a user-provided branch name or tag (ref), especially in sensitive contexts (such asrunsteps). If require, use environment variable to storehead.refand reference it to prevent injection attack. - Use caution with public repositories: Anyone can suggest changes to public repositories. Review workflow triggers, and never use self-hosted runners with public repositories.
- Restrict allowed actions: Use the Allow enterprise, and select non-enterprise, actions and reusable workflows setting to control which actions can run.
Assumptions and preconditions
This article assumes readers are familiar with GitHub Actions and have experience creating and managing workflows. It also assumes a basic understanding of security best practices and concepts such as authentication, authorization, and OIDC.
Recommended implementation
Use OpenID Connect (OIDC) for authentication
CI/CD platforms like GitHub Actions often require access to sensitive resources such as source code repositories, build artifacts, and deployment environments. To ensure that only authorized users and services can access these resources, use OpenID Connect (OIDC) for authentication.
OIDC eliminates the need for long-lived credentials in Actions secrets, reducing the risk of secret exfiltration. By establishing trust between GitHub Actions and a service that supports OIDC, attributes like organization, repository, workflow, or user can be used as conditions to approve or deny access.
Configuring trust relationships with specific claims
When configuring OIDC trust relationships with cloud providers or compliant services, specify granular claims to restrict access to trusted conditions. For example:
subclaim: The primary OIDC subject identifier, uniquely representing the repository that requested the token. Use this claim to restrict access to specific repositories and environments. Prefer an exact match on a complete claim instead of wildcard matches.job_workflow_refclaim: Specifies the exact workflow file path and commit SHA. Note that only a limited number of cloud providers support this custom claim (e.g. Azure).
Define the most granular trust conditions wherever possible to prevent unauthorized access, even from legitimate repositories. Customize the sub claim when the cloud provider’s OIDC implementation does not support matching custom claims or you need immutable identifiers in the sub.
- Prefer immutable identifiers (e.g.,
repository_owner_id:12345:repository_id:67890) over mutable ones (e.g.,repo:github/some-repo).
Scaling with reusable workflows
OIDC can also secure reusable workflows across multiple organizations and repositories. Use job_workflow_ref to ensure only approved workflows access sensitive resources.
Configure repository rules
Repository rulesets provide a strong defensive layer that complements workflow-level security measures. Consider these rules:
- Require pull requests before merging: Enforce human review to detect malicious changes. For example, require human review and at least two reviewers to merge to the default branch.
- Require status checks to pass before merging: Ensure automated validation checks pass before merging.
- Require code scanning results: Identify security vulnerabilities before merge.
- Require signed commits: Ensure all commits are signed to prove who authored them and that they haven’t been modified.
- Restrict bypass permissions: Limit bypass capabilities to emergencies and monitor via audit logs.
Implement least privilege for workflow permissions
GitHub Actions workflows include a pre-defined GITHUB_TOKEN variable that grants default permissions to jobs in the workflow. These default permissions can be configured as either permissive or restricted at the organization level or at the repository level.
Set the organization-level or repository-level default to restricted (read-only). Then, define permissions explicitly at the job level using the jobs.<job_id>.permissions section. Avoid setting workflow-level permissions globally (at the top of the workflow file) as this grants the same permissions to all jobs. This approach:
- Forces each job to declare only the specific permissions it needs
- Prevents accidental privilege escalation
- Ensures new jobs default to no permissions rather than inherited elevated permissions
- Reduces the attack surface if a job is compromised
For example, define minimal permissions on each job instead of all jobs:
name: 'My workflow'
on: [push]
# Remove permissions at the workflow-level to force jobs to define permissions
permissions: {}
jobs:
some-automate-write-job:
runs-on: ubuntu-latest
# Write permission set at job-level
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/github-script@v8Use Dependabot to protect the supply chain
Dependabot is a GitHub feature that automatically identifies outdated or vulnerable dependencies across repositories and creates pull requests to update them. By enabling Dependabot, teams can ensure that:
- Actions used in workflows are up to date
- Container images, packages, and other dependencies in the repository are secure
- The supply chain is monitored for vulnerabilities
Pin versions of actions
When using third-party actions in workflows, pin them to an immutable reference (a commit SHA or the tag from an immutable release) to avoid breaking changes, security vulnerabilities, and supply chain attacks from maliciously modified tags.
Pinning an action to a full-length commit SHA is the most reliable way to use an immutable version of an action. While GitHub offers immutable tags as an alternative, this feature can be disabled by repository owners. Pinning to a specific SHA mitigates the risk of a bad actor changing a tag to point to malicious code. When selecting a SHA, verify it comes from the action’s official repository and not a fork. Use SHAs from tagged releases so Dependabot version updates can suggest updates. If a tag is used from an immutable release, always verify a new version is also immutable.
To pin the version of an action to a commit SHA, specify the commit hash in the uses field of the workflow file. Tie the commit SHA directly to a tag version (not an arbitrary commit on the default branch) and add a comment noting the version. For example:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7Including the version number in a comment helps teams track versions and makes it easier to identify outdated actions.
Security administrators can enforce policies requiring all GitHub Actions workflows and reusable workflow references to be pinned to full-length commit SHAs. This enforcement is available at the enterprise, organization, and repository levels and can be enabled via a dedicated checkbox in the allowed actions and reusable workflows policy settings.
Avoid actions with mutable dependencies
Some actions include mutable dependencies or pull in code from external sources without verification. These actions remain mutable even after pinning to a specific commit hash, since they include components that can change behavior without altering the action’s source code. Using mutable actions dependencies introduces security risks and makes workflows vulnerable to supply chain attacks.
To avoid using actions with mutable dependencies, review actions source code and ensure they do not include elements such as unpinned container images, unpinned composite actions, or scripts that download code from external sources without verification.
Avoid workflow injection
Avoid inserting untrusted input into any executable context such as run: blocks using expressions. Values like issue titles, PR bodies, and branch names can be manipulated by attackers to inject malicious commands. Use environment variables to safely incorporate user input.
For more details on the risk of workflow script injections and mitigation strategies, refer to GitHub Docs.
Avoid pull_request_target
The pull_request_target event is a privileged workflow trigger that should be used with extreme caution. Unlike pull_request, which runs in the context of the pull request branch with read-only permissions and no access to secrets, pull_request_target runs in the context of the base repository with full access to repository secrets and write permissions.
The risk arises when a workflow using pull_request_target explicitly checks out code from the pull request and executes it. Because the workflow runs with base repository privileges, this combination allows untrusted code to execute with elevated permissions. This scenario is known as a “pwn request”.
When pull_request_target is appropriate
The pull_request_target trigger is designed for specific use cases where elevated permissions are required to interact with pull requests from forks, such as:
- Posting comments or labels on pull requests
- Updating pull request status or metadata
Avoid this trigger for public repositories, and be cautious in implementing this trigger for internal repositories.
Secure workflow_run workflows
The workflow_run event executes workflows after another workflow completes. While useful for privileged operations after validation, it carries security risks. Attackers can modify triggering workflows in pull requests, which then trigger existing workflow_run workflows with elevated permissions. Additionally, malicious artifacts uploaded by unprivileged workflows can poison privileged workflows that consume them.
When implementing workflow_run workflows:
- Use branch filters to limit where workflows can be triggered
- Verify the event origin with
github.event.workflow_run.event != 'pull_request' - Treat all artifacts as untrusted and validate contents before use
- Download artifacts to temporary directories (e.g.,
$RUNNER_TEMP) to prevent workspace pollution - Avoid defining global environment variables in
$GITHUB_ENVfrom untrusted data
Use caution with public repositories
Public repositories enable open-source collaboration but introduce significant security considerations. The most critical risk is using self-hosted runners with public repositories.
Avoid use self-hosted runners with public repositories. When a public repository is configured to use self-hosted runners, anyone who can create a fork can potentially run dangerous code on the self-hosted runner machine by creating a pull request that executes code in a workflow. This can lead to:
- Malicious programs running on the machine
- Escaping the machine’s runner sandbox
- Exposing access to the machine’s network environment
- Persisting unwanted or dangerous data on the machine
- Poisoning caches that could affect other repositories or workflows
If allowing pull requests from forks, consider enabling “require approval for all outside collaborators” to require approval before workflows run on fork PRs.
Even ephemeral self-hosted runners carry risks in public repository contexts, as attackers can exploit the window between job start and completion, or leverage shared resources like caches.
Restrict allowed actions
The allowed actions and reusable workflows setting controls which actions can run (or not run) in your organization or enterprise. This provides centralized governance and makes it easy to respond to compromised actions without searching across repositories.
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.
Additional solution detail and trade-offs to consider
Pinning actions based on a version tag
Although pinning to a commit hash is the most secure option, specifying a tag is more convenient and widely used. If specifying a tag, ensure the action’s creators are trusted. The “Verified creator” badge on GitHub Marketplace is a useful signal, but it is not a guarantee of security.
For the most secure footprint, trust no one, review the code, and always use commit SHAs.
Seeking further assistance
GitHub Support
Visit the GitHub Support Portal for a comprehensive collection of articles, tutorials, and guides on using GitHub features and services.
Can’t find what you’re looking for? You can contact GitHub Support by opening a ticket.
GitHub Expert Services
GitHub’s Expert Services Team is here to help you architect, implement, and optimize a solution that meets your unique needs. Contact us to learn more about how we can help you.
GitHub Partners
GitHub partners with the world’s leading technology and service providers to help our customers achieve their end-to-end business objectives. Find a GitHub Partner that can help you with your specific needs here.
GitHub Community
Join the GitHub Community Forum to ask questions, share knowledge, and connect with other GitHub users. It’s a great place to get advice and solutions from experienced users.
Related links
GitHub Documentation
For more details about GitHub’s features and services, check out GitHub Documentation.
Specific helpful articles:
- Security Hardening for GitHub Actions
- Self-hosted runner security
- Events that trigger workflows
- OIDC token claims
- Secure use reference
External Resources
- Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests
- Keeping your GitHub Actions and workflows secure Part 2: Untrusted input
- Keeping your GitHub Actions and workflows secure Part 3: How to trust your building blocks
- Keeping your GitHub Actions and workflows secure Part 4: New vulnerability patterns and mitigation strategies