Toutes les vulnérabilités
HIGHSupply chainexploited in the wildcurated

CVE-2025-30066

CI/CD · tj-actions/changed-files

Résumé

tj-actions/changed-files was one of the most widely used GitHub Actions on the planet, wired into the CI pipelines of more than 23,000 repositories just to list which files a pull request touched. On 14 March 2025 it was turned into a weapon. An attacker who had stolen the credentials of the project's bot account rewrote nearly every version tag, from v1 all the way to v45.0.7, so they all pointed at one malicious commit. Any pipeline that pulled the action then ran code that dumped the build runner's memory and printed the secrets it found, AWS keys, GitHub tokens, npm tokens, private keys, straight into the workflow logs. Tracked as CVE-2025-30066, it is the defining modern CI/CD supply chain attack, and a blunt lesson that a version tag is a sticky note, not a lock.

How it happened

Workflows pull this action with a line like uses: tj-actions/changed-files@v45. The weakness is that a Git tag is mutable: it is just a pointer the repository owner can move to any commit at any time. The attacker, holding a Personal Access Token belonging to the project's @tj-actions-bot account, force-updated tags v1 through v45.0.7 so every one of them resolved to a single commit, 0e58ed8. To blend in, that commit was authored under a spoofed renovate[bot] identity (an unverified commit; the real Renovate bot was never touched). The payload was buried in the action's compiled dist/index.js bundle, not the human-readable source, as a new function that ran a double base64 encoded shell command. That command pulled a Python script (memdump.py) from a public gist, found the GitHub Actions Runner.Worker process, read its memory through /proc/<pid>/mem, and regex-matched the in-memory secrets table. The ugly twist: there was no attacker server. The harvested values were double base64 encoded and printed into the job's own console output, so the secrets were exfiltrated through the build logs themselves, which on a public repository anyone can read.

The cascade that fed it

The part worth studying is that the compromise did not start at tj-actions; it arrived through a chain of trusted dependencies. Palo Alto Unit 42 traced the origin to the spotbugs/spotbugs project, where a maintainer's Personal Access Token was added to a workflow in late November 2024 and stolen through a malicious pull request around 6 December 2024. That token had write access to reviewdog, another popular Actions publisher. On 11 March 2025 the attacker used it to repoint reviewdog/action-setup@v1 to a malicious commit (tracked separately as CVE-2025-30154), ran the same memory dumping trick, then quietly reverted the tag to hide the change. Because tj-actions/changed-files used a reviewdog action inside its own CI, with a token attached, that two hour window was long enough to capture the @tj-actions-bot PAT. Three days later that stolen token was used to poison tj-actions. One leaked secret in an obscure static analysis project cascaded, dependency by dependency, into 23,000 downstream pipelines. It is a supply-chain attack on the tools that build software, the same family as Codecov and SolarWinds.

How bad it really was

The headline, 23,000 affected repositories, is the count that used the action, not the count that lost secrets. The honest blast radius was much smaller. Endor Labs reviewed the actual runs and found that only about 614 repositories executed an affected workflow during the roughly 24 hour window, and only 218 leaked a secret into their logs. Most of what leaked was the ephemeral GITHUB_TOKEN, which expires when the job ends, so it was low value; only a few dozen runs exposed long lived credentials such as Docker Hub, npm, or AWS keys. Because the exfiltration channel was the logs, the real exposure sat on public repositories where anyone could read them, while private repositories were largely shielded. And the 23,000 were never the point. Unit 42 showed the campaign began as a precise strike on Coinbase's open source agentkit project, which Coinbase confirmed did no damage; the mass spray went out hours later the same day, opportunistic, after the targeted phase failed. No actor was ever identified, only throwaway accounts deleted afterward. CISA added the flaw to its Known Exploited Vulnerabilities catalog on 18 March 2025; a clean v46.0.1 had already shipped on 16 March.

Why tj-actions still matters

The root cause is mundane and everywhere: a Git tag is mutable. Referencing an action as @v45 trusts the maintainer, and anyone who steals their token, to never move that tag to hostile code. The fix the whole industry converged on is to pin every third party action to a full length commit SHA, which cannot be moved, so even a hijacked tag cannot redirect you. The rest of the lessons echo every CI/CD breach before it: build runners hold the keys to everything, so scope CI tokens to least privilege, prefer short lived OIDC credentials over long lived secrets, and watch runner egress so a poisoned action cannot phone home. tj-actions is the incident that finally made commit pinning a default rather than a nicety, and the OpenSSF published a maintainers' guide to hardening CI/CD pipelines in its wake. If you run GitHub Actions, this is the case that should change how your workflows are written.

Comment le corriger

  • Treat every secret exposed to an affected workflow run (roughly 12 to 15 March 2025) as compromised, and rotate all CI tokens, cloud keys, and registry credentials immediately.
  • Upgrade tj-actions/changed-files to v46.0.1 or later, and pin it to that release's full commit SHA.
  • Search your "changed files" job logs for a double base64 encoded blob (decode with `base64 -d | base64 -d`) to confirm whether secrets actually leaked.
  • Audit every other third party action in your workflows and re-pin them all to commit SHAs.

Comment l’éviter

  • Pin every third party GitHub Action to a full length commit SHA, never a mutable tag or branch.
  • Default GITHUB_TOKEN to read only, and grant write scopes per job only where strictly required.
  • Replace long lived secrets with short lived OIDC credentials wherever the platform supports it.
  • Monitor and restrict CI runner egress so a compromised action cannot dump secrets or reach an external server.
  • Rotate any secret exposed to a compromised action fast, and keep a vetted, minimal set of actions instead of many community ones.

Références

Vulnérabilités liées

Tout Supply chain →