GitHub Actions Security: Your CI Pipeline Is a Dependency Graph

Compromised actions, mutable tags, impostor commits and pull_request_target: what to lock down first in your GitHub Actions supply chain.
GitHub Actions security is supply-chain security. How CI pipelines get attacked and how to pin actions, scope tokens and gate risky workflows at the PR.
June 1, 2026

A GitHub Actions workflow file is a dependency manifest that nobody reviews like one. Every uses: line pulls in code you did not write, runs it with access to your repository, your secrets and your release credentials, and resolves a version at run time unless you told it otherwise.

GitHub Actions security is supply-chain security. The same questions you ask about an npm package apply to an action, and the stakes are often higher, because the code runs inside your build with a token that can push to your repository. This post covers how the CI supply chain gets attacked, what to lock down first, and how to keep it locked down as workflows change.

Why your CI pipeline is a dependency graph

Look at a typical workflow and count what it depends on:

  • Third-party actions such as actions/checkout, a cloud login action, a linter, a release helper.
  • Reusable workflows and composite actions, which themselves call further actions, several layers deep.
  • Packages bundled inside JavaScript actions, shipped in the action's dist folder and never listed in your repository.
  • Container images used by Docker actions and service containers.
  • Tools downloaded at run time by curl in a run: step.

That is a full dependency graph with transitive edges, and in most organizations it has no inventory, no vulnerability matching and no review policy. The application's dependencies get scanned on every pull request. The pipeline that builds and ships them does not.

How the CI supply chain gets attacked

The attacks on Actions fall into a few recurring patterns.

Compromised actions

In March 2025 the widely used tj-actions/changed-files action was compromised and its version tags were repointed to a malicious commit that dumped CI secrets into workflow logs (CVE-2025-30066). Every workflow that referenced it by tag ran the malicious code on its next run. Workflows pinned to a full commit SHA of an earlier, clean version did not.

Mutable references

A reference like @v4 is a Git tag, and tags can be moved. @main is a branch, which moves by design. Either way, the code your workflow runs can change without any change to your repository.

Impostor commits

Because of how GitHub stores forks, a commit that exists only in a fork can be reachable through the parent repository's name. A workflow that pins owner/action@<sha> can end up running code the real maintainer never wrote, if that SHA came from a fork. A SHA pin is only as trustworthy as the check that the commit belongs to the real repository.

Dangerous triggers and script injection

The pull_request_target trigger runs in the context of the base repository, with access to secrets and a write-capable token, even for pull requests from forks. Combine it with a checkout of the pull request's code and you have handed untrusted code your secrets. Separately, expressions like ${{ github.event.issue.title }} interpolated straight into a run: script let anyone who can open an issue inject shell commands.

Over-broad tokens

The GITHUB_TOKEN available to a job can be granted write access to contents, packages, pull requests and more. A compromised step inherits whatever the job was given. A job that only runs tests does not need to push to your repository.

GitHub Actions security basics: what to lock down first

GitHub's own security hardening guide for GitHub Actions covers these in depth. In order of payoff:

1. Pin actions to a full commit SHA

Replace tag and branch references with the full 40-character SHA, and keep the version in a comment so humans and update bots can read it:

uses: actions/checkout@<full-40-character-sha> # v4.2.2

Take the SHA from the action's own repository, not from a fork, and let an update tool propose SHA bumps as reviewed pull requests.

2. Set token permissions explicitly

Declare the minimum at the top of every workflow and widen per job only where needed:

permissions: { contents: read }

Do not rely on the organization default. It may be read-only today and changed by someone else tomorrow.

3. Treat pull_request_target as dangerous

Use pull_request for anything that builds or tests contributed code. Reserve pull_request_target for jobs that never check out or execute the pull request's code, such as labelling.

4. Never interpolate untrusted input into scripts

Pass event data through an environment variable and quote it, instead of putting ${{ }} expressions inside run: blocks.

5. Wait before adopting a new release

A malicious release is usually detected within days. Requiring a new action version to have been public for a minimum period before you adopt it removes most of that window.

Deciding which actions to trust

Pinning stops a trusted action from changing under you. It does not tell you whether to trust the action in the first place. Before adopting a third-party action, look at:

  • The publisher. A verified publisher and an established maintainer history are better signals than a single-contributor repository created last month.
  • Provenance. Whether releases carry a signed build attestation that shows where the artifact came from.
  • Project health. Branch protection, signed releases, token permissions and maintenance activity, the kind of signal the OpenSSF Scorecard measures.
  • What it bundles. A JavaScript action ships its own dependencies. Those can carry known vulnerabilities or a compromised package of their own.
  • Whether you need it. A five-line shell step you own is often safer than an action that does the same thing.

Keeping it locked down

A one-time cleanup of workflow files does not last. New workflows are added every week, often copied from examples that use tags, and increasingly written by AI coding agents that reproduce the most common patterns in public repositories, which are tag references and broad permissions.

That means GitHub Actions needs the same controls as any other dependency ecosystem:

  • An inventory of every action and reusable workflow, direct and transitive, with the exact version each workflow references.
  • Vulnerability and compromise matching against that inventory, including the packages bundled inside actions.
  • A pull request check that blocks new unpinned actions, new compromised references and dangerous workflow patterns before they merge.
  • Detection of workflow files that change outside the normal review path.

A GitHub Actions security checklist

  • Pin every action to a full commit SHA from the real repository, with the version in a comment.
  • Declare permissions: in every workflow, starting from contents: read.
  • Remove pull_request_target from any job that checks out or runs contributed code.
  • Move untrusted event data into environment variables before it reaches a script.
  • Require a minimum age for new action releases.
  • Inventory actions and their bundled packages, and match them against advisories.
  • Gate new unpinned or compromised actions on the pull request.
  • Review workflow changes with the same care as application code, via CODEOWNERS on .github/workflows.

How Heeler secures the Actions supply chain

Heeler treats GitHub Actions as a first-class dependency ecosystem. It reads every workflow file in monitored repositories and resolves the actions and reusable workflows they reference, including the composite actions and reusable workflows those pull in, several layers deep. For JavaScript actions it tracks the packages bundled inside, so a vulnerability in an action's dependencies surfaces like one in your own. Each action is recorded at the exact version the workflow points at, whether a commit SHA, a tag or a branch, and scored on pin status, publisher trust, provenance and source health. The details are in the GitHub Actions supply chain documentation.

Findings cover impostor commits, unpinned actions, compromised actions, excessive GITHUB_TOKEN permissions and dangerous triggers. PR Guardrails can block pull requests that introduce unpinned actions, enforce a minimum age on actions and their bundled packages, and block or warn on compromised actions. Tag and branch references such as @v3, @main and @latest count as unpinned. The same minimum-age idea applies to packages across your application dependencies, so a freshly published malicious release cannot slip straight into the build.

Actions sit in the same inventory, SBOM and remediation flow as the rest of your dependencies. See how Heeler covers the pipeline end to end on the CI/CD Security page.

FAQ

Why should GitHub Actions be pinned to a commit SHA?

A commit SHA is the only immutable reference to an action's code. Tags and branches can be moved, so a workflow referencing @v4 can run different code tomorrow without any change to your repository.

What is the risk with pull_request_target?

It runs with the base repository's secrets and a write-capable token, even for pull requests from forks. Checking out and running the pull request's code under it gives untrusted code access to those secrets.

What GITHUB_TOKEN permissions should a workflow have?

Start from contents: read at the workflow level and grant additional scopes only to the jobs that need them. A test job rarely needs any write permission.

Can a pinned action still be malicious?

Yes. A SHA pin freezes whatever code you pinned, including a malicious version or an impostor commit from a fork. Pin to verified commits from the real repository and match your actions against compromise advisories.

Do the packages inside a GitHub Action matter?

JavaScript actions ship their own bundled dependencies, which run in your pipeline. A vulnerable or compromised package inside an action is as dangerous as one in your application.

Your CI pipeline is a dependency graph with access to your secrets. To see every action in it inventoried, scored and gated at the pull request alongside your application dependencies, Get a demo.

What’s new on Heeler
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Related resources

See All Resources