DevSecOps

Securing GitHub Actions: Hardening Your CI/CD Supply Chain

GitHub Actions is a powerful CI/CD platform — and a significant attack surface. Here's how to lock it down against supply chain threats.

Bob
DevSecOps Engineer
6 min read

GitHub Actions has become the de facto CI/CD platform for open source and increasingly for enterprise development. By mid-2022, it powered the build and deployment pipelines for millions of repositories. But with that ubiquity comes risk: GitHub Actions is now one of the most targeted components of the software supply chain.

The Heroku/Travis CI token theft in April 2022 demonstrated that CI/CD platforms are high-value targets. GitHub Actions, with its deep integration into the world's largest code hosting platform and its access to secrets, deployment credentials, and package registry tokens, is an especially attractive one.

Here's how to secure it.

The Threat Model

Before hardening, understand what you're defending against:

Third-Party Action Compromise

GitHub Actions workflows frequently use third-party actions from the GitHub Marketplace. These actions are GitHub repositories maintained by third parties. If an attacker compromises the action's repository — through maintainer account takeover, malicious PR, or direct code injection — every workflow that uses that action will execute the attacker's code.

Workflow Injection

Workflows that use untrusted input (pull request titles, commit messages, issue bodies) without proper sanitization can be exploited for code injection. An attacker crafts a pull request with a malicious title, and the workflow evaluates it as code.

Secret Exfiltration

Workflows have access to repository secrets, organization secrets, and environment secrets. A compromised workflow can exfiltrate these secrets to an attacker-controlled server.

Artifact Poisoning

Workflows that produce build artifacts, container images, or package releases can be manipulated to produce tampered outputs that are then distributed to consumers.

Fork-Based Attacks

Pull requests from forks can trigger workflows in the target repository. Depending on configuration, these workflows may run with access to secrets and elevated permissions.

Hardening Measures

Pin Actions to Full Commit SHAs

The single most important hardening step. Instead of referencing actions by tag or branch:

# Dangerous - tag can be moved to point to malicious code
- uses: actions/checkout@v3

# Safe - pinned to specific, immutable commit
- uses: actions/checkout@93ea575cb5d8a053eeb0ac9dc73d9c01f5f5d2c3  # v3.1.0

Tags are mutable — an attacker who compromises an action's repository can move a tag to point to a malicious commit. Commit SHAs are immutable. Even if the repository is compromised, the pinned commit remains unchanged.

Use tools like Dependabot or Renovate to automatically propose updates to pinned action versions, so you get security updates without sacrificing the safety of pinning.

Minimize Permissions with permissions Key

By default, GitHub Actions workflows receive a token with broad permissions. Use the permissions key to restrict this:

permissions:
  contents: read
  pull-requests: write

Start with permissions: {} (no permissions) at the workflow level, then grant only what each job specifically needs. This limits the damage if a workflow is compromised.

Use Environment Protection Rules

For workflows that deploy to production or publish packages, use GitHub Environments with protection rules:

  • Required reviewers — A human must approve the deployment
  • Wait timer — A delay before deployment starts, allowing time to detect issues
  • Deployment branches — Only allow deployments from specific branches

Restrict Fork PR Workflow Triggers

Configure how workflows respond to pull requests from forks:

on:
  pull_request_target:  # Runs in context of base repo - use with extreme caution
  pull_request:         # Runs in fork context - safer but limited

pull_request_target runs with the base repository's secrets and permissions, which is dangerous for PRs from untrusted forks. Prefer pull_request for untrusted inputs and use pull_request_target only when specifically needed, with careful input validation.

Sanitize Untrusted Inputs

Never use untrusted input directly in run commands:

# Vulnerable to injection
- run: echo "Processing PR: ${{ github.event.pull_request.title }}"

# Safe - use environment variable
- env:
    PR_TITLE: ${{ github.event.pull_request.title }}
  run: echo "Processing PR: $PR_TITLE"

The first form allows an attacker to inject arbitrary shell commands through the PR title. The second form treats the title as a string value, not code.

Use OIDC for Cloud Authentication

Instead of storing long-lived cloud credentials as secrets, use GitHub's OIDC provider to get short-lived tokens:

permissions:
  id-token: write

steps:
  - uses: aws-actions/configure-aws-credentials@v1
    with:
      role-to-arn: arn:aws:iam::123456789:role/my-role
      aws-region: us-east-1

OIDC tokens are scoped to specific repositories and workflows, expire quickly, and don't require storing secrets in GitHub.

Audit Third-Party Actions

Before using any third-party action:

  1. Review the action's source code
  2. Check the maintainer's reputation and activity
  3. Verify the action has a security policy
  4. Prefer actions from verified creators or the actions/ organization
  5. Consider forking critical actions into your organization and maintaining your own copy

Enable Audit Logging

For organizations, enable audit logging to track:

  • Workflow creation and modification
  • Secret access and modification
  • Self-hosted runner registration
  • Permission changes

Feed these logs into your SIEM for monitoring and alerting.

Self-Hosted Runner Security

If you use self-hosted runners:

  • Never use self-hosted runners for public repositories — any fork PR could execute code on your runner
  • Use ephemeral runners that are destroyed after each job
  • Isolate runners from your corporate network
  • Don't store credentials on runner machines
  • Keep runner software updated

Monitoring and Detection

Hardening is necessary but not sufficient. You also need to detect when something goes wrong:

Watch for Action Version Changes

Monitor your workflows for unexpected changes to action references. A pinned SHA that suddenly changes could indicate tampering.

Alert on Unusual Secret Access

Track which workflows access which secrets, and alert on deviations from the expected pattern.

Monitor Workflow Run Times

A workflow that suddenly takes much longer than usual may be performing unauthorized operations (exfiltrating data, mining cryptocurrency).

Review Workflow Run Logs

Periodically review workflow logs for unexpected commands, network connections, or error patterns.

The SLSA Connection

The Supply Chain Levels for Software Artifacts (SLSA) framework provides a graduated approach to build security. GitHub Actions is one of the first platforms to support SLSA Level 3 build provenance through the slsa-github-generator project.

Using SLSA with GitHub Actions means your builds produce a signed provenance statement that cryptographically attests to what was built, from which source, using which build process. This provenance can be verified by consumers to ensure the artifact hasn't been tampered with.

How Safeguard.sh Helps

Safeguard.sh helps organizations secure their GitHub Actions workflows as a critical component of their software supply chain. Our platform analyzes workflow configurations for security issues, monitors for unauthorized changes to CI/CD pipelines, and verifies that build artifacts include proper provenance. By integrating with your GitHub organization, Safeguard.sh provides continuous visibility into your CI/CD security posture and ensures that the hardening measures described above are consistently applied across all repositories.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.