On March 14, 2025, a popular GitHub action named tj-actions/changed-files was compromised and backdoored to dump secrets manipulated by the CI. While the origin of the compromise is still unknown, the payload added to this action is now well understood and documented.
In this blog, we'll talk about the attack's impact, patterns of secrets, and remediation steps to protect your workflows.
The first stage is a base64 string containing a shell script that downloads and executes the next stage on Linux only. The second stage is a combination of Python and Shell scripts that dumps memory, and looks for the "[^"]+":\{"value":"[^"]*","isSecret":true\}
pattern, encodes it twice with base64 and prints the results.
This mode of operation is common to other known compromises of GitHub actions, such as the Ultralytics supply chain attack from December 2024. However, the secrets are not only exfiltrated for the attacker's benefit but are also printed in the logs. If these are public and have not been deleted since 14 March, anyone can retrieve them.
This intrigued us, and we did an initial manual analysis to see if we could find any secrets ourselves. Since GitGuardian knows everything that happens on GitHub, we were able to quickly identify repositories where the compromised action was used, and extracted three distinct patterns.
The first one is not easy to identify visually. The next two images are from the same workflow. The corresponding jobs were run a few minutes apart. The first is normal: the action behaves as it should, looking for modified files. The second is different: you can see two empty lines. They indicate that the payload was executed, but no secrets were leaked.
Left: normal CI execution - Right: compromised CI execution
To reproduce this behavior, simply type the following command to mimic the second stage payload :
B64_BLOB=$(echo -n ‘No interesting secret here’ | tr -d ‘\0’ | grep -aoE ‘’[^‘]+’:\
{‘value’:‘[^’]*’, “isSecret”:true\
}' | sort -u | base64 -w 0 |base64 -w 0)
echo $B64_BLOB
The second pattern starts with the following double base64 string prefix.
Once decoded, this prefix returns the string "github_token":{"value":"ghs_. A GitHub token prefixed with ghs_ is automatically created within an action to authenticate against GitHub. By default, these tokens are very restrictive if permissions are not specified in the workflow yaml file, or if the workflow permissions settings are not changed for the GitHub repository. Moreover, they also expire at the end of the job or after 24 hours.
The third pattern, as you might expect, contains other kinds of secrets. In our manual analysis, the first one we found was a Zapier webhook. Interestingly, this type of secret is perfectly detected by GitGuardian and gghsield, unlike other competing solutions.
It is now clear that interesting and valid secrets are publicly available. The question becomes: is it possible to find them automatically and at scale?
Public Logs Analysis
Thanks to the dataset available at GitGuardian, we retrieved commits from the last 6 months that contain the tj-actions/changed-files string in a yaml file located at .github/workflows/. In total, this represents 85k patches spread across 14k different GitHub repositories.
From an attacker's perspective, they look like interesting targets as only 30% of the patches use a pinned commit of the tj-actions/changed-files GitHub actions. In other words, 70% are likely vulnerable to the March 14th attack if a corresponding job was run during the exploitation window.
We analyzed all these candidate repositories using the following steps:
- List workflow runs during the attack window
- Filter the results based on the previously observed workflow
- Collect the logs of the job candidates
- Check for the presence of leaked secrets
Our results show that only 1104 workflows ran during the attack timeframe. Among those, only 276 show a sign of exploitation. Moreover, 15 runs were affected by the attack but did not have any secrets available at the time of exploitation and, therefore did not have any leaked secrets.
At the time of analysis, 256 runs over 237 repositories had their logs deleted after following the recommended remediation steps. It still represents less than 25% of repositories.
Overall, the severity of this attack depends on the secrets available at the time of exploitation. Our research found that, out of the 603 secrets exposed in the attack, 466 (77%) are temporary GitHub tokens that are automatically generated by the CI for the duration of the workflow run. These only pose a risk if they are exploited before the workflow job expires.
This challenges the attacker’s strategy. Indeed, by choosing to leak secrets in the CI logs, the attacker was probably unable to collect them quickly and automatically. This made short-lived secrets, such as temporary GitHub tokens, more difficult to exploit. This may indicate that the attack was targeted toward a specific vulnerable repository rather than an attempt at a mass compromise.
Other secrets we found include:
- DockerHub credentials
- AWS keys
- GitHub personal access tokens
- Jira tokens
- cloud.gov credentials
It is worth noting that, at the time of testing, only 1% of the leaked secrets could be verified as valid. This, along with the amount of cleared log files, indicates that the remediation steps were considered important and performed on time.
Conclusion
The compromise of the tj-actions/changed-files GitHub Action is a perfect example of how attackers use secrets to gain further access after exploiting an initial vulnerability, achieving lateral movement that is difficult to detect.
Following our investigations, we highly recommend inspecting your GitHub actions to find out if you are affected.
In a defense-in-depth approach, you should immediately delete your GitHub actions logs and consider rotating any keys associated with workflows using tj-actions/changed-files. Last but not least, you should consider pinning all third-party actions to a full commit SHA, as recommended in the GitHub Security Hardening documentation.
From a detection standpoint, this research underscores the need to use honeytokens for early warning, as breaches are ultimately inevitable and can go unnoticed when the corresponding attacks are properly executed.

