Safeguard
Incident Analysis

GitHub Actions supply chain risk report

A look at the tj-actions/changed-files compromise and the broader trend of GitHub Actions supply chain attacks — and what security teams should do now.

Safeguard Research Team
Research
7 min read

Incident Analysis — July 2026. In the space of twelve months, GitHub Actions went from a convenience feature bolted onto the world's largest code host to the single most consequential attack surface in modern software delivery. The watershed moment came on March 14, 2025, when maintainers of tj-actions/changed-files discovered that their widely used Action had been silently rewritten to dump CI runner memory — including AWS keys, GitHub personal access tokens, npm tokens, and private RSA keys — directly into public workflow logs. By the time GitHub and the maintainers force-pushed a clean tag, an estimated 23,000 repositories had already executed the poisoned code at least once. Days later, researchers traced the root cause to a second, quieter compromise: reviewdog/action-setup@v1 (CVE-2025-30154), which had been used as the initial foothold to steal a personal access token and pivot into tj-actions. Two Actions, one supply chain, thousands of downstream victims — and a pattern that has repeated, with variations, in nearly every quarter since.

This report examines what happened, why GitHub Actions has become the preferred pivot point for supply chain attackers, and what the incident data tells security teams about where to invest next.

The Anatomy of the tj-actions Compromise

The tj-actions/changed-files incident remains the clearest case study in Actions-based supply chain compromise because its mechanics are now well documented and because it exposed a structural weakness that is still present in most CI configurations today.

  • The entry point was trust, not code. reviewdog/action-setup was compromised first, likely through a leaked or overly-permissioned token. Attackers used that access to modify a tag that dozens of downstream Actions — including tj-actions/changed-files — referenced by version label rather than commit SHA.
  • The payload targeted memory, not disk. Rather than exfiltrating files, the injected code executed a Python script that scraped the CI runner's process memory for secrets and base64-encoded the results directly into the workflow's public log output — no outbound network call was needed to trigger typical egress-monitoring alerts.
  • The blast radius was amplified by tag mutability. Because Git tags on GitHub Actions can be moved to point at a new commit, any workflow pinned to @v35 or @v44 rather than a specific 40-character SHA silently pulled the malicious version on its next run. GitHub's own advisory noted that pinning by tag, not by commit hash, was the single largest amplifying factor in the incident's reach.
  • Disclosure lagged exploitation. Multiple security vendors flagged anomalous log output in the days before the maintainer and GitHub Security Lab confirmed the compromise, meaning some organizations were exposed for a full disclosure cycle before mitigation guidance existed.

CVE-2025-30066 (tj-actions/changed-files) and CVE-2025-30154 (reviewdog/action-setup) were both rated critical, and GitHub subsequently disabled tj-actions entirely for a period while the ecosystem re-pinned dependencies.

Why GitHub Actions Is a Structurally Attractive Target

The tj-actions incident wasn't an outlier — it was a demonstration of properties that make the Actions marketplace uniquely favorable to attackers compared to other software supply chain vectors like npm or PyPI.

Third-party Actions run with the same privileges as first-party code. When a workflow references uses: some-org/some-action@v1, that Action's code executes inside the runner with access to whatever secrets, GITHUB_TOKEN scopes, and network reachability the workflow was granted — there is no sandboxing boundary between "code I wrote" and "code a stranger on the marketplace wrote."

Version pinning practices remain weak across the ecosystem. Despite repeated advisories, the overwhelming majority of public workflows still reference Actions by mutable tag (@v4) or floating branch rather than immutable commit SHA. This is precisely the gap that turned one compromised maintainer account into a 23,000-repository incident. Static analysis of public workflow files consistently finds that fewer than one in five organizations SHA-pin their third-party Actions.

Secrets routinely have more reach than the job needs. CI runners are frequently provisioned with long-lived cloud credentials, registry push access, and deployment tokens scoped far beyond the single build step that needs them — turning any Actions compromise into a potential path to production infrastructure, not just source code.

Self-hosted runners expand the blast radius further. Organizations that use self-hosted runners for cost or performance reasons often place those runners inside the corporate network with access to internal services — meaning a poisoned Action can pivot laterally in ways a GitHub-hosted, ephemeral runner cannot.

The marketplace has no meaningful vetting gate. Unlike registry-level scanning that has slowly matured for npm and PyPI, third-party GitHub Actions can be published, forked, and referenced with essentially no automated security review, and popularity/star count is a weak proxy for trustworthiness — several compromised Actions in 2024–2025 had tens of thousands of dependent repositories.

The Broader Trend: This Is Not an Isolated Event

Security teams that treated tj-actions as a one-off patched, moved on, and left the underlying exposure intact. The pattern has recurred:

  • The Ultralytics PyPI compromise (December 2024) used a poisoned GitHub Actions workflow cache-poisoning technique to inject a cryptominer into a legitimate ML package's release pipeline — showing that Actions compromise is now a viable path into package registries, not just a downstream consequence of them.
  • Multiple "star-jacking" and typosquat Actions continue to appear in the Marketplace, mimicking popular Action names with near-identical READMEs to harvest secrets from workflows that reference them by mistake.
  • Independent research groups tracking Actions-based attacks throughout 2025 catalogued dozens of malicious or compromised Actions beyond the two headline CVEs, most following the same playbook: compromise a maintainer or dependency, move a tag, harvest secrets from the next CI run.

The common thread across all of these incidents is that CI/CD pipelines are now treated by attackers as first-class production infrastructure — because functionally, they are. A compromised Action with write access to a deployment token is a more direct path to customer environments than most application-layer vulnerabilities, and it usually generates far less telemetry.

What Security Teams Should Take Away

The mitigation guidance from this incident cycle is consistent and, notably, still under-adopted:

  1. Pin every third-party Action to a full commit SHA, never a tag or branch, and monitor for upstream changes to that SHA's referenced Action repository.
  2. Scope GITHUB_TOKEN and workflow secrets to least privilege on a per-job basis rather than granting workflow-wide, long-lived access.
  3. Treat Actions as a dependency tree, not a single import — a pinned Action can still call other, unpinned Actions internally, reintroducing the exact risk you thought you'd closed.
  4. Audit workflow logs for secret exposure retroactively, since log-based exfiltration (as in tj-actions) often leaves no trace outside the log itself.
  5. Inventory which of your repositories actually executed a compromised Action version, not just which reference it — exposure and exploitation are different questions, and only the second one demands credential rotation.

That last point is where most organizations struggled most during the tj-actions response: knowing whether a workflow referenced the vulnerable version is trivial; knowing whether it ran during the exposure window, with which secrets in scope, is not — and it's the difference between a rotation exercise and a fire drill.

How Safeguard Helps

Safeguard is built for exactly this class of problem. Our platform ingests and generates SBOMs across your build pipelines — including GitHub Actions workflows and their transitive Action dependencies — so you have a queryable inventory of what actually executed, not just what's declared in a YAML file. Reachability analysis then tells you whether a flagged Action or its downstream code path was ever reachable from your actual build and runtime logic, cutting through advisory noise to focus remediation on real exposure rather than every repository that merely references a vulnerable name. Griffin AI correlates these signals against incidents like tj-actions/changed-files in near real time, flagging affected workflows, unpinned Action references, and over-scoped secrets before the next compromise finds them. When action is needed, Safeguard can open auto-fix PRs that convert floating tags to pinned commit SHAs and tighten workflow permissions automatically, turning a multi-day incident response scramble into a reviewed pull request. For security teams still auditing their GitHub Actions exposure from the last twelve months, that combination — inventory, reachability, AI-driven correlation, and automated remediation — is the difference between reacting to the next disclosure and having already closed the door.

Never miss an update

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