Post

HN
Hacker News

GitHub Actions is the weakest link

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 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.