The Oasis Research Team
Cyber Research Team
Published on
March 17, 2025
On the 14th of March 2025 a software component popular among GitHub Workflow CICD pipelines, named “tj-actions/changed-files” was infected by a threat actor with malicious code.
The malicious code leaks the secrets shared with the CICD worker process to the GitHub Workflow Logs.
For nearly 24 hours the component remained malicious, risking around 23 thousand repositories, eventually resulting in some of them leaking their secrets, until fixed.
This emphasizes the critical importance of securing software supply chains and the need to protect non-human identities, such as API tokens and CI/CD secrets, which are often targeted by attackers.
GitHub Action workflows are automated processes that enable continuous integration and continuous deployment (CI/CD) by executing a series of jobs and steps in response to specific events in a GitHub repository. GitHub Action workflows can be used for tasks such as automatically running tests on code changes, deploying an application to a cloud service whenever a new commit is pushed to a repository, checking for outdated dependencies of new code and so on…
GitHub Actions workflows are defined in YAML files, where each workflow consists of jobs that run on specified runners (such as ubuntu-latest). Each job contains a series of steps, which can either run commands or use predefined actions to execute tasks.
The following example runs npm build only if new files were added to the src folder.
Determining which files have changed in the repo can have a lot of other uses like running tests only on modified files, deploying only modified files and auto-generated release notes. One can understand how “changed-files” could become such a popular and essential component.
Once the workflow reached a step using “changed-files”, the component’s code (hosted here) would get executed.
The first file executed is index.js.
In the malicious version (available here) the entry point is the “run” function (line 1861) which in turn invokes a function named updateFeatures(), holding the malicious code-
The function takes a long base64 string, writes its decoded form to file, then executes it and writes the stdout to the github Action logs.
When decoded, the stream reveals a Bash Script-
The first thing we notice here is a reference to an external Python script.
The script is hosted on a different repository, unrelated to the first one, and for the time being was removed.
However the file can be found on the wayback machine here, this is its content-
It begins by looking for processes with names beginning with “Runner.Worker” which relates to the GitHub Action Runners, then follows to dump readable contents of their memory.
This code was hosted on the GitHub page of a researcher who was doing extensive work around GitHub Actions Workflow vulnerabilities (see one of their projects here).
The researcher is very unlikely related to the attack and his work was probably abused.
The Bash script filters the output of the Python script (a memory dump) using grep, with the following regular expression-
The regular expression matches templates like this one-
“variable name” : {“value” : “some value here”, “isSecret”:true}
Why would such strings reside in the process memory?
When aGitHub Workflow Runner Worker (project hosted here) starts, it waits and listens for a “JobRequest” message which includes required details for the job to run.
One of those details is a dict of “VariableValue” objects.
VariableValue consists of 2 members- value and isSecret, the exact ones the regexp is after.
Upon start, the entire job request message is being converted to JSON and written to the trace.
It might be that during this stage the sensitive secrets find their way to the process memory.
The matched secrets are then encoded to base64 twice, and written to the Runner Worker logs.
The following is an example of a log containing leaked secrets from an affected public repo (encoded secret masked in red)-
tj-actions author says “This attack appears to have been conducted from a PAT token linked to @tj-actions-bot” (quote from here). We can presume the old secret was single factor password-based.
The malicious commit impersonated the renovate[bot].
The Renovate Bot is an automated tool designed to help manage and update dependencies in a project. Seeing a lot of commits by the renovate[bot] is normal - it regularly scans the dependencies listed in your project (e.g., in package.json, requirements.txt, etc.) and checks if there are any newer versions available. If it finds updates, it creates pull requests to update the dependencies.
The legit renovate[bot] has had commits on the main branch of “changed-files” on almost every week since mid 2022 totalling to almost 700 commits up to date.
The attack modified existing version tags to point to the malicious commit, extending the attack surface.
In short - workflows running from 14 March 18:57 to 15 March 23:50 GMT+2 may be affected.
Oasis is a platform purpose built for inventory and management of non-human identities, such as the secrets leaked as part of this attack. In order to respond, the Oasis platform offers the following:
This way the Oasis platform enables a quick and complete response, while minimizing operational risk.