In March 2025, a single compromised GitHub Action touched over 23,000 repositories and exfiltrated CI/CD secrets — AWS keys, npm tokens, PyPI credentials, signing keys — to publicly visible workflow logs. The action in question was tj-actions/changed-files, a widely used utility for detecting file changes in pull requests. It was not a novel or obscure package; it had millions of uses across the GitHub ecosystem. That is precisely what made it an attractive target.
TL;DR: An attacker compromised a Personal Access Token with write access to the tj-actions/changed-files repository, pushed malicious code that echoed all environment variables (including secrets) to workflow logs, and affected every repository using the action via tag references. The core failure was tag pinning instead of SHA pinning — a widespread practice that creates silent, undetected dependency on whoever controls a tag at any moment in time.
How the Attack Worked: From PAT Compromise to Secrets Exfiltration
The attack chain was elegantly simple. The attacker obtained a Personal Access Token (PAT) that had write access to the tj-actions/changed-files repository. The mechanism of PAT compromise has not been definitively confirmed publicly, but common vectors include phishing, credential stuffing against GitHub accounts, or exposure of the token in another repository or service.
With write access to the repository, the attacker pushed malicious commits that modified the action's code to iterate over all environment variables available to the GitHub Actions runner and echo them to standard output. GitHub Actions automatically masks known secrets in logs — values that match configured secret patterns are replaced with asterisks. However, this masking is not comprehensive, and the attack was designed to work around it by encoding the output.
The critical detail is how the compromised code propagated. Most repositories using tj-actions/changed-files referenced it by a version tag: uses: tj-actions/changed-files@v45 or similar. A tag in Git is a mutable pointer — the repository owner can move it to point to any commit. When the attacker pushed malicious commits and retargeted version tags to point to them, every repository that ran a workflow after that moment automatically executed the malicious version. No pull request, no review, no explicit update by any of the 23,000 affected repository owners. The dependency was updated silently and globally.
What Was Exposed and the Business Impact
The GitHub Actions runner environment contains everything injected as a secret or environment variable for the workflow. This typically includes:
- Cloud credentials: AWS access key IDs and secret access keys, GCP service account keys, Azure client secrets. These are the most immediately dangerous — an attacker with live cloud credentials can begin exfiltrating data or provisioning resources within minutes.
- Package registry tokens: npm, PyPI, RubyGems, and Maven publish tokens. Compromise of a publish token enables a secondary supply chain attack: pushing malicious versions of the affected package to public registries.
- Signing keys: Code signing certificates, GPG keys, and similar materials used to establish artifact integrity. These are particularly damaging because they undermine the trust model of the entire software distribution chain.
- Third-party API credentials: Stripe, Twilio, SendGrid, and other SaaS integrations whose credentials are injected as secrets for deployment or testing workflows.
The repositories affected ran the gamut from individual open source projects to enterprise software. For open source repositories, the workflow logs are publicly visible, meaning exfiltrated credentials appeared in plain sight. For private repositories, the logs are not public, but the attack code could have been designed to exfiltrate via external channels — making the public log approach a lower-sophistication version of what a more targeted attacker would do.
The secondary supply chain risk: Any organisation whose code signing key or package registry token was exposed faces a compounded problem. Even if they rotate the credential, they must audit every release signed or published using that key for signs of tampering. The downstream consumers of those packages inherit that uncertainty.
Tag Pinning vs SHA Pinning: Why It Matters
The fundamental architectural failure in this incident — and in the vast majority of similar supply chain attacks — is tag pinning. When a workflow file references uses: some-action/name@v3, it is expressing a dependency on whatever commit the v3 tag points to at the moment the workflow runs. That is a decision made by the action's maintainer, not by the repository owner, and it can change at any time.
SHA pinning is the correct approach. A SHA reference — uses: tj-actions/[email protected] — is immutable. It refers to a specific commit that cannot be retroactively changed. When you pin to a SHA, you know exactly what code you are running, and an attacker cannot modify it without also modifying the reference in your workflow file, which requires access to your repository.
The practical objection to SHA pinning is maintenance overhead: you must manually update SHA references when you want to adopt upstream changes. Tools like Dependabot and Renovate can automate this, generating pull requests when upstream actions release new versions. This turns the security property into a reviewable, auditable update process rather than a silent automatic update.
| Pinning Strategy | Supply Chain Security | Update Mechanism | Auditability |
|---|---|---|---|
| Tag pinning (@v3) | None — maintainer can change at any time | Silent, automatic | None |
| SHA pinning (@sha256) | Strong — immutable reference | Explicit PR via Dependabot/Renovate | Full diff visible in PR |
| Vendored action | Strongest — code in your own repo | Manual copy on update | Full code review possible |
| Unversioned (@main) | None — worst option | Continuous, silent | None |
Auditing Your CI/CD for Similar Risks
If you use GitHub Actions, the immediate audit is straightforward: search your workflow files for uses: references that do not include a full SHA. A simple search across your repositories for this pattern will surface all tag-pinned and unversioned action references. GitHub's own security features can assist here — the dependency graph and Dependabot configuration can be used to enumerate external action dependencies.
Beyond pinning, audit your secrets configuration. Review which secrets are available to which workflows, and apply the principle of least privilege. Secrets required only for deployment to production should not be available in pull request workflows that may be triggered by external contributors. GitHub supports environment-scoped secrets that restrict sensitive credentials to specific deployment environments and require manual approval gates.
Review whether your workflows run on pull requests from forks. Fork-based pull request workflows can trigger on code submitted by anonymous external contributors, and if your pipeline exposes production secrets to those workflows, the attack surface is substantial. GitHub provides the pull_request_target trigger specifically to handle this scenario with appropriate restrictions, but its correct usage is nuanced and commonly misconfigured.
SLSA Framework and GitHub Artifact Attestation
The tj-actions incident is exactly the threat model that the SLSA (Supply-chain Levels for Software Artifacts) framework was designed to address. SLSA defines four levels of supply chain integrity, each adding additional guarantees about provenance and tamper-resistance. At SLSA Level 2, build provenance is generated and signed, establishing a verifiable record of where and how an artifact was built. At higher levels, the build environment itself is verified to match expectations, making it substantially harder for an attacker to introduce malicious code undetected.
GitHub has implemented artifact attestation as part of its Actions ecosystem, allowing workflows to generate cryptographically signed provenance records for build outputs. These attestations can be verified before deployment using the GitHub CLI: gh attestation verify. For organisations building software that others depend on, implementing artifact attestation is a concrete step toward the supply chain integrity guarantees that SLSA describes.
Lorikeet Security's CI/CD security assessments evaluate pipeline configuration, secret management practices, action pinning strategy, branch protection rules, and artifact integrity — the full set of controls that determine whether your build infrastructure could be weaponised against your users. If you are uncertain about the current state of your pipeline security, a structured assessment is the most efficient way to identify gaps before an incident makes them visible for you.
Is Your CI/CD Pipeline a Security Liability?
Lorikeet Security evaluates GitHub Actions, GitLab CI, Jenkins, and CircleCI pipelines for secret exposure, insecure triggers, missing branch protections, and supply chain risks. Understand your build infrastructure's attack surface before it becomes an incident.