GitHub Actions is a powerful automation tool that enables CI/CD workflows directly within your GitHub repository. Securing your GitHub Actions workflows is crucial to protect your code, secrets, and infrastructure from potential security threats.
This guide outlines best practices for securing your GitHub Actions workflows and minimizing security risks. All actions used in committed workflow definitions must be pinned to a full-length commit SHA.
- Secrets Management
- Limiting Permissions
- Third-Party Actions
- Dependency Management
- Runner Security
- Pull Request Workflows
- OIDC Integration
- Audit and Monitoring
This section describes how secrets in GitHub Actions should be managed, teams should ensure that they are using robust secrets management tools such as Azure Key Vault and AWS Secrets Manager for securely storing secrets.
- Store sensitive data (API tokens, credentials, etc.) as GitHub Secrets
- Never hardcode sensitive values in your workflow files
- Do not use structured data as a secret - this can cause GitHubs secret redaction in logs to fail
- Rotate secrets regularly
- Use environment-specific secrets when possible
- Ensure a secret scanner is deployed as part of your workflows
- Public repositories should enable GitHub Secret Scanner and Push Protection
# Good practice - limiting secret to specific environment
jobs:
deploy:
environment: production
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Deploy
env:
API_TOKEN: ${{ secrets.API_TOKEN }}
run: ./deploy.sh- Don't echo or print secrets in workflow steps
- Set debug to false when using secrets
- Use masking for any dynamically generated secrets
Limit the GitHub token permissions to only what's necessary see here for details on the default permissions that the github token is given when the permissions block is not used:
permissions:
contents: read
pull-requests: write
issues: writeFine grained tokens must only be used if the GitHub token can not be used.
- Create custom GitHub Apps with limited scopes when possible
- Use repository-scoped tokens instead of organization-wide tokens
While third-party actions can significantly enhance the functionality and efficiency of your workflows, they also introduce potential security risks:
- Untrusted Code: Third-party actions are often maintained by external developers. If the code is not reviewed or vetted, it may contain vulnerabilities or malicious code that could compromise your repository or infrastructure.
- Version Drift: Using tags like @latest or branch references (e.g., @main) can lead to unexpected changes in behavior if the action is updated. This could introduce breaking changes or vulnerabilities into your workflows.
- Dependency Vulnerabilities: Third-party actions may rely on outdated or insecure dependencies, which could expose your workflows to known vulnerabilities.
- Lack of Maintenance: Some third-party actions may not be actively maintained, leaving them vulnerable to security issues or compatibility problems with newer GitHub Actions features.
- Excessive Permissions: Third-party actions may request more permissions than necessary, potentially exposing sensitive data or allowing unauthorized access to your repository.
To mitigate these risks, all actions must be pinned to specific commit SHAs, reviewed before adoption, and sourced only from trusted publishers. Teams must minimise use of third-party actions and should expect the permitted set of actions to be restricted over time.
When including a GitHub Action within your workflow you should perform due diligence checks to ensure that the action achieves the aims you are intending it to, and that it does not do anything unintended, including reviewing the action code where appropriate. Every action reference must use a full-length commit SHA, including GitHub-authored actions, marketplace actions, and internally maintained actions, and must include an inline comment identifying the corresponding tag or version. Do not use tags or branch references in committed workflow definitions because they can move without review or be modified if the upstream repository is compromised. The tag annotation comment is not optional β without it, a pinned SHA is opaque and cannot be reviewed or updated effectively:
# Not secure - can change unexpectedly
- uses: actions/checkout@v4
# Also not acceptable - tags can be moved
- uses: actions/checkout@v4.1.7
# Required - pin to the full commit SHA and annotate the tag for readability
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7If you use automation such as Dependabot to keep actions up to date, enable the github-actions ecosystem in dependabot.yml and keep the release tag comment on the same line as the pinned SHA so updates continue to track tagged releases.
A minimal Dependabot configuration for GitHub Actions is:
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 7The cooldown option tells Dependabot to delay version updates for newly released versions until they have aged past the configured threshold. This reduces the chance of immediately pulling in a compromised release. Security updates are not subject to cooldown, so known vulnerabilities are still flagged immediately.
Third-party actions must not be the default choice. Before introducing one, teams should confirm that the requirement cannot be met by:
- Native GitHub Actions features such as
runsteps, reusable workflows, or built-in workflow syntax - An action already owned and maintained within the organisation
- An action that is already approved for reuse by other teams
If a third-party action is still required, document why it is needed, what alternatives were considered, and why those alternatives were rejected. This should live in docs/ADRs.md, or similar, to ensure the decision process is held within the repository. Teams should prefer actions with a clear maintenance history, minimal permissions, and a narrow, well-understood scope.
If you can only achieve your goal with a third-party action then:
- Only use trusted actions from the GitHub Marketplace
- Review the source code of third-party actions before using them
- Consider forking and maintaining your own copy of critical actions
- Keep a record of the approval decision and the version or SHA that was reviewed
- Be prepared to replace the action if organisational policy restricts the allowed set of actions
The long-term direction is to lock down the set of actions that can be used. Teams should therefore avoid introducing new third-party actions unless there is a clear, defensible need.
- Enable Dependabot alerts for GitHub Actions
- Set up a workflow that regularly checks for outdated actions
- Use dependency scanning tools like GitHub's Dependabot
- Implement automated dependency updates
- Regularly review and update dependencies with security patches
Self-hosted runners must only be used with private repositories. If using self-hosted runners:
- Run them in isolated environments (containers/VMs)
- Regularly update and patch runner machines
- Implement proper network isolation
- Use ephemeral runners when possible
jobs:
build:
runs-on: [self-hosted, isolated]
steps:
# Your workflow steps here- Be aware that GitHub-hosted runners are reset after each job
- Clean up any sensitive data before job completion
- Don't store persistent sensitive data in the runner's environment
- Don't expose secrets to pull request workflows from forks
- Do not use
pull_request_targetin workflows. It runs in the context of the base repository with access to secrets and write permissions, even for PRs from untrusted forks. This makes it a common vector for supply chain attacks ("pwn requests"). Usepull_requestinstead
# Required approach for PR workflows
on:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Run tests
run: npm testThe one widely-used exception is Dependabot auto-merge workflows. GitHub's own auto-merge guidance relies on pull_request_target because Dependabot PRs need write access to trigger merges. If you use this pattern, restrict permissions to the minimum required (e.g. pull-requests: write, contents: write), and ensure the workflow never checks out or executes code from the PR head. Any such workflow must be reviewed and approved before merging.
To ensure the workflow never checks out or executes PR head code:
- Do not include an
actions/checkoutstep. Auto-merge workflows only need to call the GitHub API (e.g. to approve or merge); they do not need the repository contents. - If a checkout is unavoidable, never use the PR head ref or SHA β do not pass
ref: ${{ github.event.pull_request.head.sha }}orref: ${{ github.head_ref }}toactions/checkout. Omittingrefchecks out the base branch. - Do not interpolate any PR-supplied values directly into
run:steps. This applies to all workflows, not justpull_request_targetβ see Avoid Script Injection below. - Limit the workflow trigger. Scope
pull_request_targetto specific activity types (e.g.opened,synchronize) and, where possible, add a condition to restrict execution to Dependabot only:
on:
pull_request_target:
types: [opened, synchronize]
jobs:
auto-merge:
if: github.actor == 'dependabot[bot]'Script injection can occur in any workflow β not just pull_request_target β whenever untrusted values are interpolated directly into a run: step. GitHub Actions expressions are evaluated before the shell executes the command, so an attacker who controls an input value (e.g. a PR title, branch name, or issue body) can inject arbitrary shell commands.
Do not use ${{ }} expressions directly inside run: blocks. Instead, assign them to environment variables and reference those variables in the shell:
# Unsafe - expression evaluated before shell runs; attacker controls PR title
- run: echo "${{ github.event.pull_request.title }}"
# Safe - value passed via environment variable; shell treats it as data, not code
- run: echo "$PR_TITLE"
env:
PR_TITLE: ${{ github.event.pull_request.title }}This applies to all context values that may contain user-controlled input, including but not limited to:
github.event.pull_request.title/.body/.head.refgithub.event.issue.title/.bodygithub.event.comment.bodygithub.head_ref
Values sourced entirely from within your own organisation (e.g. github.repository, github.sha) carry lower risk, but the environment variable pattern is still the preferred style for consistency and reviewability.
- Enforce branch protection rules
- Require code reviews before merging
- Use status checks to enforce security scans
Instead of storing long-lived cloud credentials, use GitHub's OIDC provider:
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions
aws-region: eu-west-2- Set specific subject claims in your cloud provider
- Implement additional claim conditions (repository, branch, environment)
- Monitor GitHub Actions usage via audit logs
- Set up alerts for suspicious activity
- Enforce code reviews for workflow file changes
- Use CODEOWNERS to restrict who can modify workflow files
# CODEOWNERS file/.github/workflows/ @security-team
- Conduct regular reviews of all workflows
- Update security practices based on emerging threats
- Monitor GitHub security advisories
- GitHub Actions Security Hardening Guide
- GitHub Security Lab
- GitHub Actions Documentation
- Security for GitHub Actions
Securing GitHub Actions requires a multi-layered approach focusing on secrets management, permissions, third-party action vetting, and proper configuration. By following these best practices, you can significantly reduce security risks while still enjoying the full benefits of GitHub Actions automation.
Remember that security is an ongoing process - regularly review and update your security practices to adapt to new threats and challenges.