tj-actions/changed-files - Stolen PAT → Retag to Malicious Commit → Runner Memory Dump → Secrets in Public Logs → 23,000+ Repos Exposed
In March 2025 attackers ran a chained supply chain compromise (CVE-2025-30066) against tj-actions/changed-files, one of the most widely used GitHub Actions. Using a stolen bot Personal Access Token, they retroactively updated many version tags to point at a single malicious commit that scraped the CI runner's memory and printed captured secrets - AWS keys, GitHub PATs, npm tokens, and private RSA keys - into publicly readable GitHub Actions build logs. Over 23,000 repositories referenced the action, though only dozens are confirmed to have leaked live credentials. The initial foothold traced back to a compromised PAT tied to reviewdog/action-setup (CVE-2025-30154), making this a supply-chain attack on a supply-chain tool.
tj-actions/changed-files detects which files changed in a pull request or commit and is one of the most popular GitHub Actions, referenced by more than 23,000 repositories. Because so many public repositories invoked it - often pinned only to a mutable version tag like v45 rather than a commit SHA - a single poisoned release would fan out into thousands of CI/CD runs. Attackers who could push to the action would inherit the trust and the secrets of every downstream workflow that ran it.
Blast radius: 23,000+ repositories referencing the action
Why it worked: most callers pinned to mutable tags (v45, v44...) not immutable commit SHAs
The tj-actions maintainers disclosed that the root cause was a compromised GitHub Personal Access Token (PAT) belonging to the tj-actions-bot account. That PAT was exposed earlier through reviewdog/action-setup@v1 (CVE-2025-30154), which had itself been poisoned to dump secrets. tj-actions depended on reviewdog transitively via tj-actions/eslint-changed-files, so credentials leaked during the reviewdog attack were reused to gain write access to changed-files. Analysts including Unit 42 traced the initial foothold further upstream through SpotBugs' GitHub Actions workflow, making this a multi-hop chained supply-chain compromise.
Upstream chain: SpotBugs workflow → reviewdog/action-setup (CVE-2025-30154) → tj-actions-bot PAT
Dependency link: tj-actions/eslint-changed-files relied on reviewdog/action-setup
With write access, the attackers committed a malicious payload and then rewrote many of the action's existing version tags so that they all resolved to that single commit. Consumers who trusted a tag such as v45 - assuming it was an immutable, reviewed release - silently began running attacker code on their next CI run. The injected code was a Node.js function containing base64-encoded instructions that downloaded a Python script from a GitHub gist. Because the tags were mutable, no consumer had to change a line of their workflow to be affected.
Dropper: Node.js function with base64-encoded instructions pulling a Python script from a GitHub gist
Stealth: mutable tags meant existing workflows executed the payload with no config change
The downloaded Python script scanned the memory of the GitHub Actions runner process, where workflow secrets are held in cleartext during a job, and harvested any credentials it found. Captured material included valid AWS access keys, GitHub Personal Access Tokens, npm tokens, and private RSA keys (T1552.004). Rather than exfiltrate to an external server, the payload printed the stolen secrets directly into the job's standard output as a double-encoded base64 blob - a low-noise technique that needed no outbound C2 channel and blended into normal log volume.
Harvested: AWS access keys · GitHub PATs · npm tokens · private RSA keys (T1552.004)
Encoding: secrets emitted to the build log as a double base64-encoded payload
For public repositories, GitHub Actions logs are world-readable, so any secret written to the log by the payload was exposed to the entire internet during the March 12-15, 2025 window. More than 23,000 repositories referenced the action, but Wiz Threat Research confirmed live secret leakage in only dozens of cases - the rest either did not run the affected job with real secrets, used private repos, or caught it before rotation. The malicious gist and commit were taken down around March 15, and the maintainers shipped a clean release, v46.0.1. Any public workflow that ran between roughly 2025-03-12 00:00 and 2025-03-15 12:00 UTC should treat all its CI secrets as compromised and rotate them.
Confirmed impact: 23,000+ repos referenced it; dozens confirmed to leak live secrets (Wiz)
Remediation: gist/commit removed ~Mar 15; fixed in v46.0.1; rotate all secrets from the exposure window
