If your repositories used tj-actions/changed-files at any point, you need to act now. In March 2025, security researchers found that every version tag of this popular GitHub Action had been redirected to malicious code that dumped secrets into workflow logs. AWS keys, GitHub tokens, npm credentials—all sitting there in plaintext for anyone with read access to see.
The attack exploited how most teams reference GitHub Actions. It demonstrates a weakness in the ecosystem: teams pin to mutable tags instead of immutable commit SHAs. This incident exemplifies the broader challenges we explore in our software supply chain security guide, where CI/CD pipelines have become primary attack vectors.
So here’s what you need to do: work out if you’re affected, rotate any exposed credentials, and fix your workflows so this can’t happen again. This article walks through the incident response process and the preventive controls that actually work.
What Happened in the tj-actions/changed-files Compromise?
On March 14, 2025, security vendors including Wiz, Step Security, and Checkmarx reported that tj-actions/changed-files contained malicious code designed to steal secrets from CI/CD runners. This action helps developers detect which files changed in a pull request, and it’s embedded in over 23,000 repositories.
The attack redirected every version tag—v1 through v45—to a single malicious commit. When workflows ran, the compromised code scanned runner memory for sensitive data and dumped it into workflow logs. Anyone with access to those logs could read the secrets.
CVE-2025-30066 was assigned to this incident. While approximately 218 repositories actually exposed secrets during the compromise window, the potential impact was way bigger because tj-actions/changed-files was so widely deployed.
The attack exploited a trust model weakness in how the ecosystem references GitHub Actions. Workflow logs are readable by anyone with repository access—and publicly accessible on public repos—so the exfiltration happened through workflow logs.
How Did the Reviewdog Compromise Enable the tj-actions Attack Chain?
The attack started weeks earlier when adversaries compromised the reviewdog GitHub organisation. They targeted the reviewdog/action-setup action and related tools to steal credentials, including a GitHub Personal Access Token belonging to a tj-actions maintainer.
With that PAT, attackers performed what’s called tag mutation. They redirected every existing version tag in tj-actions/changed-files to point to a malicious commit. Because most users referenced version tags instead of immutable commit SHAs, tens of thousands of workflows unknowingly pulled the attacker’s code.
The attack chain followed this sequence: reviewdog PAT theft, tj-actions tag mutation, runtime memory dumping on CI/CD runners, then log-based exfiltration. Each stage looked legitimate in isolation, which is why it was so hard to detect.
This upstream-to-downstream cascade is a hallmark of supply chain attacks: compromise one trusted component to reach all its dependents. This pattern mirrors the build system compromise attacks where adversaries targeted CI/CD infrastructure to inject malicious code at scale. The reviewdog compromise (CVE-2025-30154) gave attackers the access they needed. tj-actions became the delivery mechanism.
The malicious code used runtime memory dumping to extract secrets from the runner’s process memory and environment variables. It bypassed GitHub’s standard secret masking by encoding credentials in a way the redaction system didn’t catch. The secrets appeared in logs in unredacted form, accessible to all users with read access.
What Secrets Were Exposed and How Were They Stolen?
Once executed, the malicious code scanned the GitHub runner’s memory for AWS keys, personal access tokens, npm tokens, and private keys. It dumped these secrets into the workflow logs, exposing them to anyone with repository access.
Here’s what was at risk: GitHub secrets injected into environment variables, the GITHUB_TOKEN, cloud provider keys for AWS, Azure, and GCP, npm and PyPI tokens, RSA private keys, and any other sensitive data in the runner’s process memory.
On public repositories, this exposure was visible to the world. On private repositories, anyone with read access could view the logs.
What this meant for you depended on what types of secrets were exposed. Leaked cloud provider credentials could enable infrastructure access. Leaked deployment tokens could enable supply chain attacks on your customers. Even read-only tokens created risk, though lower than write-capable credentials.
The affected actions weren’t limited to tj-actions/changed-files. The compromise also hit reviewdog/action-setup, reviewdog/action-shellcheck, reviewdog/action-composite-template, reviewdog/action-staticcheck, reviewdog/action-ast-grep, reviewdog/action-typos, and tj-actions/eslint-changed-files.
If you used any of these in your workflows, assume any secret or sensitive information that workflow accessed has been leaked to all users with read access to the repository.
How Do I Check If My Organisation Was Affected?
Start by inventorying all repositories in your organisation that reference tj-actions/changed-files in any workflow file. These live in .github/workflows/ directories in files with .yml or .yaml extensions.
Use GitHub’s code search or the Checkmarx detection tool to identify repositories that used the action during the compromise window. Transitive relationships matter. If an otherwise-safe action uses the malicious action, you’re still at risk.
Examine workflow run logs from the affected period for indicators of compromise. Look for unusual output patterns, base64-encoded content, or exposed credential values. The free, open-source tool “2MS (Too Many Secrets)” from Checkmarx Zero can help you quickly identify secrets in downloaded log files.
Check GitHub audit logs for any unusual API activity that might indicate stolen credentials were already exploited. When attackers target public repositories as entry points and those repositories serve as dependencies for thousands of downstream projects, their compromise provides immediate access to hundreds of potential targets.
And don’t forget to check both current and historical workflow configurations. You may have removed the action already, but logs from the compromise window still contain exposed secrets.
What Is the Immediate Response Checklist for Affected Organisations?
Priority one: rotate all secrets that were available to any workflow using tj-actions/changed-files. This includes GitHub PATs, cloud provider credentials, deployment tokens, npm/PyPI tokens, and any other secrets configured in repository or organisation settings.
If you have public repositories that use one of the compromised actions, assume that any secrets used in GitHub Workflows for those repositories have been leaked and should be rotated. For internal or private repositories, be aware that secrets may have been leaked to any user with any level of access.
Priority two: replace tj-actions/changed-files with a verified alternative. Step Security provides step-security/changed-files as a drop-in replacement. Or you can use native Git commands within your workflow to detect changed files.
Whatever replacement you choose, pin it by commit SHA rather than tag to prevent the same class of attack.
Priority three: review GitHub audit logs and cloud provider access logs for evidence of credential misuse during and after the compromise window. Look for API calls, resource access, or configuration changes you don’t recognise.
Priority four: if your organisation publishes packages or actions that may have been built with compromised credentials, notify downstream consumers. They need to know their supply chain may be affected.
Priority five: document the incident scope and response actions for compliance and post-incident review. Record the timeline, affected systems, response actions, and lessons learned.
You may also need to restrict access to GitHub repositories temporarily, changing public repositories to private or internal for your organisation until you complete the audit and credential rotation.
How Do I Prevent Tag-Based Supply Chain Attacks in the Future?
The primary defence against tag mutation attacks is commit SHA pinning. Reference GitHub Actions by their full 40-character immutable commit hash rather than mutable version tags. For a comprehensive approach to securing your CI/CD environment beyond immediate incident response, see our comprehensive GitHub Actions hardening guide.
SHAs are immutable. Tags aren’t. An attacker who compromises an action’s repository can simply move the v4 tag to point to malicious code, instantly affecting every workflow using that tag.
In August 2024, GitHub announced policy support for blocking and SHA pinning actions. Administrators can now enforce SHA pinning through the allowed actions policy. Any workflow that attempts to use an action that isn’t pinned will fail.
Because this is a new policy, GitHub administrators must explicitly opt in. Tools available to perform the pinning include gh-pin-actions extension for the GitHub CLI and RenovateBot.
Once pinned, Dependabot will automatically submit pull requests with the correct new commit SHA when updates are available. Use Dependabot or Renovate to automate SHA-pinned dependency updates so security doesn’t come at the cost of maintenance burden.
Migrate from stored long-lived credentials—PATs, service account keys—to OIDC-based authentication for cloud provider access. OIDC provides short-lived, scoped authentication tokens generated at runtime rather than stored secrets. These tokens are generated on-demand and expire quickly. Because OIDC tokens aren’t stored in GitHub secrets or environment variables, they can’t be exfiltrated through log dumping. All major cloud providers support OIDC integration with GitHub Actions.
Scope GITHUB_TOKEN permissions to the minimum required for each workflow using explicit permissions blocks. Set permissions to read-only by default, and grant write only when necessary. Repositories created before February 2023 likely still have overprivileged GITHUB_TOKEN defaults with read/write permissions, while newer repositories default to read-only.
Implement a third-party action audit process. Review the action’s source code for suspicious behaviour—network calls, environment variable access, log output. Check the maintainer’s reputation, contribution history, and responsiveness to security issues. Verify the action’s dependency graph for transitive risks. Assess the permissions the action requires and whether they follow least privilege principles.
Store secrets in environment secrets instead of repository secrets. Secrets defined at the repository level are accessible to all workflows with no way to limit access. Environment secrets let you set branch policies to limit which branches have access, and you can require manual approval for production keys.
And here’s a blanket rule: don’t rely on pull_request_target. This trigger runs workflow code with write permissions in the context of the base repository, even when triggered by pull requests from forks. This enables “pwn request” attacks where malicious pull requests can execute arbitrary code with elevated privileges. For most scenarios, the pull_request trigger is sufficient.
What Are the Long-Term Lessons for CI/CD Security Strategy?
The tj-actions compromise demonstrates that CI/CD pipelines are now high-value targets for attackers, not just infrastructure to keep running. You might secure your production systems carefully, then hand the keys to your kingdom to YAML files you barely audit, using actions from strangers on the internet, triggered by events you don’t fully control.
Tag-based supply chain attacks exploit a trust model weakness: the assumption that a version tag always points to the same code. The attack pattern of compromising upstream maintainers to reach downstream targets mirrors tactics seen in the SolarWinds and XZ Utils incidents, suggesting you’re facing a persistent and evolving threat category.
The breach resulted from four misconfigurations colliding: weak event triggers, excessive permissions, mutable dependencies, and poor runtime isolation. If any one of these controls had been enforced—least-privilege tokens, SHA pinning, secret scanning, or ephemeral runners—the attack chain would have broken.
Building resilience requires defence in depth. No single control is sufficient alone, but together SHA pinning, OIDC, permission scoping, and audit procedures reduce the attack surface. These preventive controls are detailed in our hardening GitHub Actions workflows article, which covers implementation specifics for long-term CI/CD security.
You need a dependency governance policy that covers not just application packages but also CI/CD tooling, build actions, and deployment scripts. As attackers increasingly target GitHub Actions, organisations must move beyond reactive security measures. Combine webhook monitoring with audit log analysis to establish visibility into your CI/CD pipeline risks.
As we’ve seen with compromises like this, attackers are investing time to understand your supply chain. It’s time to invest equally in understanding and monitoring your attack surface. For a complete security strategy covering frameworks, operational practices, and tool selection across your software supply chain, see our broader security context guide.
FAQ Section
What is CVE-2025-30066 and how severe is it?
CVE-2025-30066 refers to the tj-actions/changed-files compromise. It’s critical for any organisation that used the action during the compromise window, as it could expose cloud credentials, deployment tokens, and other sensitive secrets.
How do tag mutation attacks differ from traditional software supply chain compromises?
Tag mutation attacks exploit the mutable nature of Git tags. An attacker with repository write access can silently redirect a tag like v1 or v45 to point to a malicious commit. Traditional supply chain compromises typically involve injecting malicious code into a new release. Tag mutation is stealthier because no new version is published—the existing trusted version silently changes.
What is the difference between SHA pinning and tag references for GitHub Actions?
SHA pinning references an action by its full 40-character commit hash, which is immutable and always points to the exact same code. Tag references like @v4 are mutable and can be redirected to different commits by anyone with write access to the repository. SHA pinning prevents tag mutation attacks but requires tooling to manage updates.
Should I rotate secrets even if I’m not sure my organisation was affected?
Yes. If any repository in your organisation used tj-actions/changed-files during the compromise window, treat all secrets available to those workflows as potentially exposed. The cost of unnecessary rotation is far lower than the risk of compromised credentials being exploited.
What is OIDC and why does it prevent credential exfiltration?
OIDC provides short-lived, scoped authentication tokens generated at runtime rather than stored as long-lived secrets. Because OIDC tokens aren’t stored in GitHub secrets or environment variables, they can’t be exfiltrated through log dumping. Major cloud providers support OIDC integration with GitHub Actions.
How do I audit third-party GitHub Actions before adopting them?
Review the action’s source code for suspicious behaviour like network calls, environment variable access, and log output. Check the maintainer’s reputation, contribution history, and responsiveness to security issues. Verify the action’s dependency graph for transitive risks. Assess the permissions the action requires and whether they follow least privilege principles.
What is the pull_request_target trigger and why is it dangerous?
The pull_request_target trigger runs with the permissions of the target branch even when a PR is opened from a fork, granting access to repository secrets. This makes it particularly dangerous when handling untrusted contributions. For most scenarios, the pull_request trigger is sufficient.
Can GitHub’s built-in secret masking prevent this type of exfiltration?
The malicious code bypassed GitHub’s process for redacting secrets from run logs by encoding them in ways the masking system didn’t recognise. Secret masking is a useful layer but shouldn’t be relied upon as the sole defence. Combine it with OIDC, permission scoping, and SHA pinning for comprehensive protection.
How does the reviewdog compromise relate to the tj-actions incident?
The reviewdog compromise (CVE-2025-30154) was the initial stage of the attack. Attackers compromised the reviewdog organisation’s actions to steal a GitHub PAT belonging to a tj-actions maintainer. That stolen PAT was then used to mutate all tj-actions/changed-files tags to point to malicious code. The reviewdog compromise was the enabler; tj-actions was the delivery mechanism.
What should I use instead of tj-actions/changed-files?
Step Security provides step-security/changed-files as a drop-in replacement. Or you can use native Git commands within your workflow to detect changed files. Whatever you choose, pin it by commit SHA rather than tag to prevent the same class of attack.
How do I check workflow logs for evidence of secret exposure?
Use the GitHub API to retrieve workflow run logs from the compromise window. Scan them with the Checkmarx 2MS scanner or TruffleHog for patterns indicating exposed credentials—base64-encoded strings, key formats matching cloud providers, token patterns. Pay attention to any unusual output that doesn’t match expected workflow behaviour.
Are self-hosted GitHub Actions runners safer than GitHub-hosted runners?
Self-hosted runners can provide more control over the execution environment but introduce different risks: persistent state between runs, network access to internal resources, and longer credential exposure windows. Unlike GitHub-hosted runners that are destroyed after each job, self-hosted runners persist and can be permanently compromised. Neither runner type is inherently safer; each requires specific hardening.