The runner holds your deploy keys, and nobody put it in the scanning program
Codecov in 2021 and tj-actions/changed-files in March 2025 failed the same way: trusted third-party code running with pipeline privileges on a push nobody on your team made.
The Engineer · Build desk
What happened
- The CI runner holds deploy keys, registry credentials, cloud tokens and signing secrets, and runs with more privilege than any developer, while going unreviewed on most projects.
- The 2021 Codecov bash uploader compromise exposed secrets from thousands of CI environments.
- In March 2025, tj-actions/changed-files was compromised and leaked secrets into build logs of every repository that referenced it by tag.
- The writeup names pull_request_target combined with checking out the pull request head as the most reliable way to leak credentials from a public repository.
Why it matters
- Who's exposed exposure Every action referenced by tag hands a third party a standing right to change what your next build executes, with no review event on your side to catch it.
- Who has to decide decision Anyone needing privileged work on fork contributions now has to pick: split into an unprivileged build and a privileged artifact consumer, or keep carrying a known credential leak.
- What it costs cost Older repositories left on default token permissions pay for a single compromised step with rewritable history and releases signed as CI.
The mechanism in both incidents is dull, which is part of why it keeps working. Trusted third-party code, running with pipeline privileges, on somebody else's push [4]. Roughly four years separate the Codecov bash uploader compromise from the tj-actions/changed-files compromise [19], and the referencing pattern that made the second one possible is still the default way workflow files name what they run.
That pattern is the tag. `uses: some-org/some-action@v3` resolves through a pointer that the upstream repository owner controls, so a compromised upstream account turns `v3` into attacker code on your next run with no change and no approval on your side [5]. A full commit SHA cannot be repointed [6]. The inventory of your exposure to this is one command: `grep -rn "uses:.*@v" .github/workflows/` [7]. Whatever else is true about supply chain security, the cost of finding out is not the obstacle here. The dev.to author's own expectation is that the count comes back worse than people guess [20].
The privilege arithmetic is where this gets expensive. Repository-wide secrets are readable by every workflow in the repository, including the lint job and including a job that runs a third-party formatter against a fork's pull request [8]. So the least-reviewed job in the repo carries the same read access as the deploy job, and the deploy key's blast radius is set by the sloppiest YAML file rather than the most careful one. The proposed test is a good one because it is falsifiable: for each secret, name the single job that needs it, and treat anything you cannot assign as over-shared [17]. Same logic on the token. A workflow with no `permissions:` block runs on the repository default, and on older repositories that default is write, which means a compromised step can push commits, edit releases or open pull requests wearing your CI's identity [12].
The failure that ties it together is detection. Log masking matches exact strings, so a secret that has been base64 encoded, JSON wrapped, or split across a variable expansion goes through unmasked [14], and on a public repository the logs are public and the artifacts are downloadable [15]. That is the tj-actions shape exactly: the leak arrived in build logs of every repository that referenced the action by tag [3].
One caveat on the evidence. This is a single practitioner writeup, and it offers no measurement of how many repositories are still tag-pinned or still running on default token permissions. The two named compromises are load-bearing; the prevalence is asserted, not counted.
What to watch
- Whether any platform vendor makes SHA pinning or read-only default token permissions the enforced default rather than a documented recommendation.
- Whether a measured survey appears showing what share of public repositories still reference third-party actions by tag.
- The next action compromise, and whether the leak path is again build logs and downloadable artifacts on public repositories.
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence52
- Adoption24
- Hype gap+18
- Incentives34
- Confidence55
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
A CI pipeline holds deploy keys, registry credentials, cloud tokens and signing secrets, runs on every push, runs third-party code the team did not write, and runs with more privilege than any developer on the team; for most projects it is completely unreviewed.
- [2]
The Codecov bash uploader compromise in 2021 exposed secrets from thousands of CI environments.
- [3]
In March 2025 the widely used tj-actions/changed-files action was compromised and began leaking secrets into the build logs of every repository that referenced it by tag.
- [4]
Both attacks worked the same way: trusted third-party code, running with pipeline privileges, on somebody else's push.
- [5]
A tag is a pointer; whoever controls the repository controls where it points, so if that account is compromised, v3 silently becomes attacker code on the next run with nothing changed and nothing approved by the consumer.
- [6]
The recommended fix is pinning every third-party action to a full commit SHA, because a SHA cannot be moved, with a comment recording the version.
- [7]
Running grep -rn "uses:.*@v" .github/workflows/ lists every unpinned action in a repository.
- [8]
Repository-wide secrets can be read by every workflow, including the lint job and including a job that runs a third-party formatter on a fork's pull request; a secret is exposed to everything that can read it.
- [9]
The recommended fix is to scope secrets to environments rather than the repository and require approval for environments holding production credentials, giving test jobs nothing.
- [10]
Using pull_request_target and then checking out the pull request head runs attacker-controlled code with secrets in scope, described as the single most reliable way to leak credentials from a public repository.
- [11]
For privileged work on a fork's contribution, the recommended split is an unprivileged job that builds and uploads an artifact and a separate privileged job that consumes the artifact without ever checking out the fork's code.
- [12]
A workflow with no permissions: block gives the automatic token the repository default, which on older repositories is write, so any compromised step in that job can push commits, edit releases or open pull requests as the CI identity.
- [13]
The recommended fix for token scope is to default to read and grant upward per job, for example contents: read at the top level and contents: write only on a release job.
- [14]
Log masking catches exact string matches and does not catch a secret that has been base64 encoded, JSON wrapped, or split across a variable expansion.
- [15]
Build logs on public repositories are public and artifacts are downloadable.
- [16]
An attacker who compromises a developer laptop gets the laptop; an attacker who compromises the pipeline gets production, without needing a code vulnerability or a phished developer.
- [17]
The suggested secrets audit is to list repository secrets and name the single job that needs each one, treating anything that cannot be assigned to a specific job as over-shared.
- [18]
The suggested check for the unsafe trigger is grep -rn "pull_request_target" .github/workflows/, with every hit requiring a written justification.
- [19]
Roughly four years separate the 2021 Codecov bash uploader compromise and the March 2025 tj-actions/changed-files compromise.
- [20]
The author states that the result of the unpinned-action grep is usually worse than expected.
Get this ledger inside ChatGPT. Ask about any of our stories and the answer brings its figure, each claim’s verdict and the sources. Free, no account.
Sources
1 independent publisher whose own reporting we read for this story.
- dev.toYour CI Pipeline Is the Most Privileged Machine You Own. You Have Never Scanned It.
1 article · August 21, 2026
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.
Topics
- Secrets ManagementFollow
- Security Tooling Coverage GapsFollow
- Software Supply Chain AttacksFollow
- GitHub Actions HardeningFollow
- CI/CD Pipeline SecurityFollow
- Least-Privilege CI TokensFollow