On March 15, 2025, the security community discovered that tj-actions/changed-files, one of the most popular GitHub Actions with over 23,000 dependent repositories, had been compromised in a software supply chain attack. The attacker modified the action's code to dump CI/CD secrets from runner memory and write them to workflow logs, where they could be harvested.
The incident was a stark demonstration of the fragility of the CI/CD supply chain and the outsized trust that organizations place in third-party GitHub Actions.
What Happened
The attack unfolded in several stages:
Stage 1: Initial compromise. The attacker gained access to a maintainer's credentials for the tj-actions/changed-files repository. The exact method of credential compromise was not immediately disclosed, but it likely involved a stolen personal access token (PAT) rather than a compromised GitHub account with 2FA enabled.
Stage 2: Code modification. Using the compromised credentials, the attacker pushed a malicious commit to the repository. The commit modified the action's entrypoint script to include code that:
- Enumerated all environment variables available to the GitHub Actions runner.
- Extracted secrets from the runner's memory, including
GITHUB_TOKEN, AWS credentials, and any other secrets injected into the workflow environment. - Wrote the extracted secrets to the workflow log output as base64-encoded strings.
Stage 3: Tag manipulation. Critically, the attacker updated existing git tags to point to the malicious commit. This meant that any workflow referencing tj-actions/changed-files@v44 (or any other version tag) would now pull the compromised code. This is a fundamental weakness of tag-based versioning in GitHub Actions: tags are mutable references that can be pointed to arbitrary commits.
Stage 4: Harvesting. The attacker monitored public GitHub Actions logs (which are visible on public repositories) for the base64-encoded secrets. For private repositories, the logs are not publicly visible, but the secrets would still be exposed to anyone with repository access.
The Blast Radius
The scale of potential impact was enormous. Over 23,000 repositories used tj-actions/changed-files, including repositories belonging to major technology companies, financial institutions, and government agencies. Any repository that ran a workflow using this action during the compromised window was potentially affected.
The compromised window was relatively short -- approximately 12 hours before the attack was detected and the malicious commits were reverted. However, for repositories with frequent CI/CD activity, even a short window could mean dozens of workflow runs executed with the compromised action.
The most significant risk was to repositories that:
- Used the action with tag-based references (
@v44) rather than commit SHA pinning. - Had workflows triggered by pull requests, pushes, or schedules during the compromised window.
- Injected sensitive secrets (cloud credentials, deployment keys, API tokens) into their workflow environments.
Why Tag-Based References Are Dangerous
This incident exposed a structural weakness in how GitHub Actions are consumed. The majority of users reference actions using version tags:
- uses: tj-actions/changed-files@v44
This looks like a version pin, but it is not. Git tags are mutable. The owner of the repository (or anyone who compromises their access) can point the v44 tag to a different commit at any time. When a workflow runs, GitHub resolves the tag to whatever commit it currently points to.
The secure alternative is to pin actions to specific commit SHAs:
- uses: tj-actions/changed-files@abc123def456789...
Commit SHAs are immutable. Even if an attacker compromises the repository, they cannot change what code a specific SHA points to. However, SHA pinning is inconvenient: SHAs are hard to read, they need to be manually updated when new versions are released, and they don't communicate version information.
This tradeoff between convenience and security is the same one we see throughout software supply chain management. Convenience usually wins until there is a breach.
The Cascading Trust Problem
The tj-actions compromise also revealed a cascading trust issue. Several other GitHub Actions depended on tj-actions/changed-files as a transitive dependency. If Action A uses Action B, and Action B uses tj-actions/changed-files, then Action A's users are also affected by the compromise even though they never directly referenced tj-actions.
This is the same transitive dependency problem that plagues every package ecosystem (npm, PyPI, Maven), but in the CI/CD context the stakes are higher. A compromised build action has access to the secrets and permissions of the workflow it runs in, which typically include repository write access, deployment credentials, and cloud service accounts.
Industry Response
The incident prompted several responses:
GitHub published guidance recommending that organizations:
- Pin actions to commit SHAs rather than tags.
- Audit their workflows to identify which actions they depend on.
- Use GitHub's allow-list feature to restrict which actions can run in an organization.
- Enable workflow run log retention policies to limit secret exposure windows.
StepSecurity released an open-source tool that could audit GitHub Actions workflows for tag-based references and automatically convert them to SHA-pinned references.
CISA included the incident in its supply chain security advisories, using it as a case study for CI/CD pipeline risks.
Lessons Learned
Pin your actions to commit SHAs. It is less convenient but dramatically reduces your exposure to compromised action repositories.
Minimize secrets in CI/CD. The principle of least privilege applies to CI/CD runners. Only inject the specific secrets each workflow step needs, and use short-lived credentials where possible.
Audit your action dependencies. Know which third-party actions your workflows depend on, including transitive dependencies. Treat them with the same scrutiny you apply to code dependencies.
Monitor workflow logs for anomalies. Unexpected output in workflow logs, particularly base64-encoded blobs, can indicate compromise.
Consider self-hosted or vendored actions. For critical workflows, fork the actions you depend on into your own organization and reference the fork. This insulates you from upstream compromises, though it adds maintenance burden.
How Safeguard.sh Helps
Safeguard.sh extends supply chain security to your CI/CD pipelines, not just your application code. By analyzing your GitHub Actions workflows as part of your software supply chain, Safeguard can identify risky patterns like tag-based action references and flag third-party actions with known security incidents.
Safeguard's SBOM capabilities cover the full scope of your software supply chain, including build dependencies, CI/CD tooling, and infrastructure components. When a supply chain attack like the tj-actions compromise occurs, Safeguard helps you quickly determine whether your workflows were affected and which secrets may have been exposed.
With Safeguard's policy gates, you can enforce organizational standards like SHA pinning for all GitHub Actions references, preventing the vulnerable patterns that make supply chain attacks possible in the first place.