Safeguard
Software Supply Chain Security

tj-actions/changed-files GitHub Action compromise

How the tj-actions/changed-files GitHub Action compromise (CVE-2025-30066) leaked CI/CD secrets from 23,000+ repos, and how to prevent it.

Vikram Iyer
Cloud Security Engineer
6 min read

On March 14, 2025, attackers compromised tj-actions/changed-files, a GitHub Action used by more than 23,000 repositories to detect which files changed in a pull request. The attackers modified the action's code and retroactively force-updated every version tag—from v1 through v45—to point to a single malicious commit, 0e58ed8671d6b60d0890c21b07f8835ace038e67. That commit added a Python payload that dumped CI runner memory, searched it for secrets, and printed them—base64-encoded—directly into the public workflow logs of any repository that ran the action. GitHub removed the repository within roughly 24 hours, but by then any public repo that had triggered a workflow using the action was potentially exposed. The incident, tracked as CVE-2025-30066, is one of the clearest recent examples of how a single trusted, transitively-included dependency in a CI/CD pipeline can become a mass secrets-exfiltration vector.

What exactly happened in the tj-actions/changed-files compromise?

Attackers gained write access to the tj-actions/changed-files repository and altered the action to leak CI/CD secrets into build logs. Security firm StepSecurity first flagged anomalous behavior on March 14-15, 2025, after its runtime monitoring detected the action's changed-files.py script writing unexpected base64-encoded data to stdout during a customer's workflow run. Investigation showed the malicious commit had been silently pushed and then every existing git tag (v1, v2, v3.x, v35–v45, and the commonly-pinned v44/v45 releases) was rewritten to reference it, so pipelines pinned to a version number rather than a commit SHA pulled the compromised code automatically on their next run. The payload used a Runner.Worker.dll memory-dumping technique to extract environment variables and in-memory secrets, then printed them to the workflow log disguised as normal output.

How many repositories and organizations were affected?

Roughly 23,000 repositories use tj-actions/changed-files, and GitHub's Security Lab and multiple vendors (Wiz, Endor Labs, Semgrep, StepSecurity) confirmed that any of them running the action between March 14 and March 15, 2025 in a public repository had a realistic chance of leaking secrets into publicly viewable logs. Because GitHub Actions logs on public repositories are viewable by anyone—including logs from workflows triggered by external pull requests in many default configurations—the blast radius wasn't limited to the affected org's own team; anyone who scraped or searched public Actions logs during that window could potentially harvest exposed credentials. Wiz's telemetry at the time estimated single-digit percentages of scanned customer environments had actually executed the compromised versions, which is still a meaningful number for a two-day exposure window on a widely-used action.

What data could attackers actually steal?

Attackers could steal anything present in the CI runner's process memory or environment at execution time, including AWS access keys, GitHub personal access tokens (PATs), npm publish tokens, and private RSA/SSH keys. The dumped values were double-base64-encoded before being printed, which was likely an attempt to slip past naive secret-scanning regexes in log-ingestion pipelines rather than to actually hide the data from a human reviewer—decoding it was trivial. Because the leak happened via stdout in the workflow log rather than through an outbound network call, many existing egress-monitoring and DLP controls built to catch supply chain attacks (which typically watch for exfiltration to attacker-controlled domains) never triggered at all.

What was the root cause of the compromise?

The root cause was a compromised credential with write access to the tj-actions/changed-files repository, and researchers traced the initial foothold back to a separate, earlier compromise of reviewdog/action-setup@v1 (tracked as CVE-2025-30154), a different popular Action used in adjacent workflows. Wiz's post-incident analysis found that the reviewdog compromise had exposed a personal access token belonging to the tj-actions-bot account; that leaked PAT then gave the attacker direct write access to push the malicious commit and retag every release of tj-actions/changed-files. This is a textbook transitive dependency problem: teams that had never directly depended on reviewdog were still exposed because the two projects shared infrastructure and credentials several hops upstream.

How was the compromise detected and remediated?

The compromise was detected by StepSecurity's runtime behavioral monitoring on March 14-15, 2025, and GitHub responded by deleting the tj-actions/changed-files repository outright to stop further pulls, before restoring a cleaned version under stricter controls a day later. The maintainer confirmed the incident publicly, rotated the compromised bot credentials, and re-published verified, SHA-pinned releases; GitHub also assigned GHSA-mrrh-fwg8-r2c3 and CVE-2025-30066 with a CVSS score of 8.6 (High). Remediation guidance from GitHub, StepSecurity, and Wiz converged on the same core recommendation: any org that ran an affected version needed to treat every secret available to those workflows as compromised—rotating AWS keys, GitHub tokens, npm tokens, and any credentials injected as environment variables into the affected pipelines, not just scanning for signs of misuse.

How can teams prevent the next GitHub Actions supply chain attack?

Teams can prevent recurrence primarily by pinning Actions to immutable commit SHAs instead of mutable version tags, since the entire attack worked because tags like v45 could be silently repointed to a malicious commit. GitHub's own advisory and CISA's alert on the incident both recommended SHA-pinning (e.g., uses: tj-actions/changed-files@0e58ed86...) plus enabling Dependabot version-pinning checks, restricting which Actions are allowed to run via organizational allow-lists, and auditing workflow logs for base64 blobs or other anomalous stdout patterns. None of that helps, though, if a team doesn't know which of its hundreds of pipelines reference the action in the first place, which is why the incident renewed attention on maintaining an accurate, queryable inventory of every third-party Action, its pinned reference, and the secrets each workflow can reach.

How Safeguard Helps

Safeguard's SBOM ingestion pipeline builds a live inventory of every GitHub Action, its version pin, and the workflows that reference it, so when an incident like tj-actions/changed-files breaks, you get an immediate answer to "are we exposed, and where" instead of a manual repo-by-repo grep. Reachability analysis goes further by mapping which of your workflows actually execute the compromised code path and have secrets in scope, cutting a 23,000-repo industry-wide alert down to the handful of pipelines that genuinely need credential rotation. Griffin AI, Safeguard's security reasoning engine, correlates the advisory against your dependency graph and CI configuration to prioritize response by real blast radius rather than by CVSS score alone. When remediation is confirmed, Safeguard's auto-fix PRs convert vulnerable tag references to SHA-pinned commits across every affected repository automatically, closing the exact gap this attack exploited without requiring a manual PR per repo.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.