Skip to content
Tech News
← Back to articles

GitHub Actions is the weakest link

read original get GitHub Actions Workflow Kit → more articles
Why This Matters

This article highlights the critical vulnerabilities in GitHub Actions that have led to numerous open source supply chain incidents, exposing the platform's default configurations and features as significant security risks. As the backbone of many open source projects, these weaknesses pose threats to both developers and consumers by enabling malicious code execution and data breaches. Addressing these issues is essential for strengthening the security and trustworthiness of the open source ecosystem and CI/CD pipelines.

Key Takeaways

Pick almost any open source supply chain incident from the past eighteen months and trace it back, and you end up reading a .github/workflows YAML file. Ultralytics shipping a crypto miner to PyPI, the nx packages that turned thousands of developer machines into credential harvesters, tj-actions leaking secrets from 23,000 repositories, Trivy getting compromised twice in three weeks, elementary-data publishing a malicious wheel ten minutes after a stranger left a GitHub comment. Different headline payloads, different victims, and in each case a GitHub Actions feature behaving exactly as documented.

I wrote in December about the narrow problem of Actions being a package manager with no lockfile, no integrity hashes and no transitive visibility, and that the uses: line is a dependency declaration that the runner re-resolves on every execution against mutable git tags. That argument still stands and has since been demonstrated rather thoroughly in production, but it’s only one face of a larger problem.

The whole product is a collection of features that are each convenient on their own and very easy to assemble into something dangerous, and the workflows building and publishing most of the world’s open source run on a platform whose defaults were chosen for a private-repo enterprise CI tool and never really rethought for anonymous forks and drive-by pull requests.

The incidents

The earliest link in the recent chain is spotbugs in November 2024, which had a workflow on the pull_request_target trigger that checked out and built code from an untrusted fork. That trigger exists so that workflows can do things like label PRs from forks, and to make that work it runs in the context of the base repository with full secret access and a write-scoped token.

Combining it with a checkout of the fork’s head.sha hands an attacker code execution inside your trust boundary, which is what happened: a malicious PR lifted a maintainer’s PAT, that PAT had access to reviewdog, and four months later the same actor used it to seed the tj-actions/changed-files compromise. GitHub’s own documentation has warned about this combination since 2021 and still ships the trigger with no guardrail beyond a paragraph in the docs.

A month after spotbugs, Ultralytics was hit through the same trigger with a different second stage. The fork PR couldn’t reach the publishing credentials directly, so instead it poisoned a GitHub Actions cache entry, and when the legitimate release workflow later restored that cache it executed the payload while building wheels. Two versions of ultralytics reached PyPI with a miner inside.

The cache is keyed by branch and shared down to children, the pull_request_target job runs as the default branch, and nothing in the UI or the API tells you that an entry was written by a job processing untrusted input.

The tj-actions incident in March 2025 is the one most people have heard of because CISA put out an advisory and because the original target turned out to be Coinbase. With the PAT harvested from spotbugs the attacker pushed a malicious commit to reviewdog/action-setup and moved the v1 tag to point at it. tj-actions/eslint-changed-files referenced reviewdog by tag, tj-actions/changed-files referenced that, and 23,000 downstream repositories referenced changed-files by tag. Every one of them ran a memory scraper that dumped runner secrets into public build logs.

The platform feature at fault is that action versions are git refs in someone else’s repository, force-pushable by anyone with write access to that repository, and consumed by default through a moving tag rather than a content hash.

... continue reading