Find information about security best practices when you are writing workflows and using GitHub Actions security features.
Writing workflows
Use secrets for sensitive information
Because there are multiple ways a secret value can be transformed, automatic redaction is not guaranteed. Adhere to the following best practices to limit risks associated with secrets.
- Principle of least privilege
- Any user with write access to your repository has read access to all secrets configured in your repository. Therefore, you should ensure that the credentials being used within workflows have the least privileges required.
- Actions can use the
GITHUB_TOKENby accessing it from thegithub.tokencontext. For more information, see Contexts reference. You should therefore make sure that theGITHUB_TOKENis granted the minimum required permissions. It's good security practice to set the default permission for theGITHUB_TOKENto read access only for repository contents. The permissions can then be increased, as required, for individual jobs within the workflow file. For more information, see Use GITHUB_TOKEN for authentication in workflows.
- Mask sensitive data
- Sensitive data should never be stored as plaintext in workflow files. Mask all sensitive information that is not a GitHub secret by using
::add-mask::VALUE. This causes the value to be treated as a secret and redacted from logs. For more information about masking data, see Workflow commands for GitHub Actions.
- Sensitive data should never be stored as plaintext in workflow files. Mask all sensitive information that is not a GitHub secret by using
- Delete and rotate exposed secrets
- Redacting of secrets is performed by your workflow runners. This means a secret will only be redacted if it was used within a job and is accessible by the runner. If an unredacted secret is sent to a workflow run log, you should delete the log and rotate the secret. For information on deleting logs, see Using workflow run logs.
- Never use structured data as a secret
- Structured data can cause secret redaction within logs to fail, because redaction largely relies on finding an exact match for the specific secret value. For example, do not use a blob of JSON, XML, or YAML (or similar) to encapsulate a secret value, as this significantly reduces the probability the secrets will be properly redacted. Instead, create individual secrets for each sensitive value.
- Register all secrets used within workflows
- If a secret is used to generate another sensitive value within a workflow, that generated value should be formally registered as a secret, so that it will be redacted if it ever appears in the logs. For example, if using a private key to generate a signed JWT to access a web API, be sure to register that JWT as a secret or else it won’t be redacted if it ever enters the log output.
- Registering secrets applies to any sort of transformation/encoding as well. If your secret is transformed in some way (such as Base64 or URL-encoded), be sure to register the new value as a secret too.
- Audit how secrets are handled
- Audit how secrets are used, to help ensure they’re being handled as expected. You can do this by reviewing the source code of the repository executing the workflow, and checking any actions used in the workflow. For example, check that they’re not sent to unintended hosts, or explicitly being printed to log output.
- View the run logs for your workflow after testing valid/invalid inputs, and check that secrets are properly redacted, or not shown. It's not always obvious how a command or tool you’re invoking will send errors to
STDOUTandSTDERR, and secrets might subsequently end up in error logs. As a result, it is good practice to manually review the workflow logs after testing valid and invalid inputs. For information on how to clean up workflow logs that may unintentionally contain sensitive data, see Using workflow run logs.
- Audit and rotate registered secrets
- Periodically review the registered secrets to confirm they are still required. Remove those that are no longer needed.
- Rotate secrets periodically to reduce the window of time during which a compromised secret is valid.
- Consider requiring review for access to secrets
- You can use required reviewers to protect environment secrets. A workflow job cannot access environment secrets until approval is granted by a reviewer. For more information about storing secrets in environments or requiring reviews for environments, see Using secrets in GitHub Actions and Managing environments for deployment.
Good practices for mitigating script injection attacks
Recommended approaches for mitigating the risk of script injection in your workflows:
Use an action instead of an inline script
The recommended approach is to create a JavaScript action that processes the context value as an argument. This approach is not vulnerable to the injection attack, since the context value is not used to generate a shell script, but is instead passed to the action as an argument:
uses: fakeaction/checktitle@v3
with:
title: ${{ github.event.pull_request.title }}
Use an intermediate environment variable
For inline scripts, the preferred approach to handling untrusted input is to set the value of the expression to an intermediate environment variable. The following example uses Bash to process the github.event.pull_request.title value as an environment variable:
- name: Check PR title
env:
TITLE: ${{ github.event.pull_request.title }}
run: |
if [[ "$TITLE" =~ ^octocat ]]; then
echo "PR title starts with 'octocat'"
exit 0
else
echo "PR title did not start with 'octocat'"
exit 1
fi
In this example, the attempted script injection is unsuccessful, which is reflected by the following lines in the log:
env:
TITLE: a"; ls $GITHUB_WORKSPACE"
PR title did not start with 'octocat'
With this approach, the value of the ${{ github.event.pull_request.title }} expression is stored in memory and used as a variable, and doesn't interact with the script generation process. In addition, consider using double quote shell variables to avoid word splitting, but this is one of many general recommendations for writing shell scripts, and is not specific to GitHub Actions.
Using workflow templates for code scanning
Code scanning allows you to find security vulnerabilities before they reach production. GitHub provides workflow templates for code scanning. You can use these suggested workflows to construct your code scanning workflows, instead of starting from scratch. GitHub's workflow, the CodeQL analysis workflow, is powered by CodeQL. There are also third-party workflow templates available.
For more information, see Code scanning and Configuring advanced setup for code scanning.
Restricting permissions for tokens
To help mitigate the risk of an exposed token, consider restricting the assigned permissions. For more information, see Use GITHUB_TOKEN for authentication in workflows.
Mitigating the risks of untrusted code checkout
Similar to script injection attacks, untrusted pull request content that automatically triggers actions processing can also pose a security risk. The pull_request_target and workflow_run workflow triggers, when used with the checkout of an untrusted pull request, expose the repository to security compromises. These workflows are privileged, which means they share the same cache of the main branch with other privileged workflow triggers, and may have repository write access and access to referenced secrets. These vulnerabilities can be exploited to take over a repository.
For more information on these triggers, how to use them, and the associated risks, see Events that trigger workflows and Events that trigger workflows.
For additional examples and guidance on the risks of untrusted code checkout, see Preventing pwn requests from GitHub Security Lab and the Dangerous-Workflow documentation from OpenSSF Scorecard.
For detailed guidance on deciding whether to use pull_request_target, hardening these workflows, and opting out of the actions/checkout protection, see Securely using pull_request_target.
Good practices
-
Avoid using the
pull_request_targetworkflow trigger if it's not necessary. For privilege separation between workflows,workflow_runis a better trigger. Only use these workflow triggers when the workflow actually needs the privileged context. -
Avoid using the
pull_request_targetandworkflow_runworkflow triggers with untrusted pull requests or code content. Workflows that use these triggers must not explicitly check out untrusted code, including from pull request forks or from repositories that are not under your control. Workflows triggered onworkflow_runshould treat artifacts uploaded from other workflows with caution. -
CodeQL can scan and detect potentially vulnerable GitHub Actions workflows. You can configure default setup for the repository, and ensure that GitHub Actions scanning is enabled. For more information, see Configuring default setup for code scanning.