Incident · GitHub Action compromise

The tj-actions/changed-files compromise: what it did, and why Kilobat’s part is small

On 14 and 15 March 2025, a GitHub Action used in more than 23,000 repositories dumped the secrets of every workflow that ran it into the workflow’s own log. This all happened on CI runners, not on developers’ Macs, so this page is mostly about what to check in your repositories, and it says plainly how little Kilobat could have seen.

When: 14–15 March 2025Sources checked: 1 November 2026

In short

  • Protection: Wouldn’t help

    The attack ran on GitHub’s runners. Its one download came from gist.githubusercontent.com, and nothing was sent to an outside server at all.

  • Agent guard: Wouldn’t help

    No agent was involved, and the secrets left through log output, not through a connection.

  • Agent network log: Wouldn’t help

    Workflows run with act run in Docker, whose traffic Kilobat can’t attribute to an agent.

  • App traffic: Limited

    Only on a Mac that runs workflows itself, as a self-hosted runner: the app list would show the runner’s connections, including the gist download. Nothing would be blocked.

What happened

GitHub’s advisory for CVE-2025-30066 says the attacker changed most of the action’s existing version tags, from v1.0.0 on, to point at one malicious commit, 0e58ed8. Every workflow that used the action by tag got that code on its next run. It downloaded a Python script from a GitHub gist and ran it with sudo; the script found GitHub’s Runner.Worker process, read its memory, picked out the values marked as secrets and printed them, base64-encoded twice, into the log. Workflows pinned to a full commit hash were not affected.

On a public repository, workflow logs are public, so the secrets were readable by anyone. Wiz found dozens of repositories with exposed secrets; the risk to private repositories was much smaller. StepSecurity spotted it because its runner monitoring flagged an unexpected call to gist.githubusercontent.com.

The tj-actions maintainers said a personal access token of their bot account had been compromised. Unit 42 traced the chain further back: a workflow pushed to the SpotBugs repository leaked a maintainer’s token, which had write access to reviewdog/action-setup; that action was compromised in turn (CVE-2025-30154), and a tj-actions workflow that used it leaked the bot’s token. Unit 42 found the first target was a Coinbase open-source project, where the attacker didn’t manage to use the secrets.

Timeline

  1. November 2024

    Unit 42 dates the attacker’s first steps to this month.

  2. 11 Mar 2025

    A workflow pushed to SpotBugs leaks a maintainer’s token; reviewdog/action-setup@v1 is compromised between 18:42 and 20:31 UTC (Unit 42, CISA).

  3. 14 Mar, about 16:00 UTC

    The action’s tags point at the malicious commit; StepSecurity detects it and confirms most versions are affected within the hour.

  4. 15 Mar 2025

    The gist is deleted; GitHub takes the action down and restores it cleaned (14:00 and 22:00 UTC, by StepSecurity’s timeline). Version 46.0.1 is the fix.

  5. 18 Mar 2025

    StepSecurity finds several reviewdog actions also compromised; CISA publishes its alert.

  6. 26 Mar 2025

    CISA’s alert notes both CVEs in its Known Exploited Vulnerabilities catalogue.

  7. 2 Apr 2025

    Unit 42 publishes the SpotBugs and Coinbase steps that led to the compromise.

What it looked like on the network

Everything happened on CI runners. The malicious step made one outbound request, to gist.githubusercontent.com, for memdump.py; the rest was local to the runner. The secrets never went to an attacker’s server: they were written into the log, and GitHub served that log to whoever looked.

The published script finds the runner process through Linux’s /proc file system, and the sources describe its effect on Linux runners. The stolen tokens were then usable from anywhere, against GitHub and the other services they belonged to.

Indicators

As the sources document them. Domains are text, never links, and are defanged ([.]) where the source defangs them. Most of this infrastructure is long gone: indicators help you check old logs, files and accounts, not block today’s campaigns.

IndicatorTypeWhere it showsNote
0e58ed8671d6b60d0890c21b07f8835ace038e67Committj-actions/changed-filesThe commit the rewritten tags pointed to.
gist.githubusercontent.com/nikitastupin/30e525b776c409e03c2d6f328f254965/raw/memdump.pyURLCI runnerThe memory-dumping script. Deleted on 15 March 2025.
A double-base64 string in the changed-files step’s logLog outputWorkflow logsPresent if the payload ran. Decoded, it holds the run’s secrets.
reviewdog/action-setup@v1, 11 March 2025, 18:42–20:31 UTCAction, time windowWorkflowsCVE-2025-30154; also affected other reviewdog actions that use it.
tj-actions/changed-files, 12 March 00:00 to 15 March 12:00 UTCAction, time windowWorkflowsThe window CISA asks you to audit.

What Kilobat would have shown or blocked

  • App traffic. If your Mac is a self-hosted runner and ran the action, Kilobat’s app list would have shown the runner’s processes connecting to gist.githubusercontent.com. That host isn’t on any threat list, and shouldn’t be: gists are used legitimately all the time.
  • Agent network log. Running a workflow on the Mac with act, even from an AI agent, puts the steps inside Docker containers. Their connections leave through Docker’s own process, which Kilobat can’t attribute to the agent, so the agent network log wouldn’t list them.
  • Protection. Protection had nothing to act on. If a token from a leaked log was later used against you, that use happened from the attacker’s machines, against GitHub.

Where Kilobat wouldn’t have helped

  • The compromise, the secret dump and the leak all happened in GitHub Actions. A tool on your Mac can’t see someone else’s runner.
  • Secrets left through log output that GitHub served normally, not through a connection anyone could block.
  • The script was fetched from a GitHub-hosted gist, a host any network filter has to allow.
  • Docker containers and VMs on a Mac are opaque to Kilobat’s per-process view.

Check your Mac now

  1. List the workflows in a repository that use the affected actions.

    grep -rnE "tj-actions/changed-files|tj-actions/eslint-changed-files|reviewdog/action-" .github/workflows
  2. Or search all your repositories on GitHub at once.

    gh search code "tj-actions/changed-files" --owner YOUR-ACCOUNT-OR-ORG
  3. For runs between 12 and 15 March 2025, open the changed-files step’s log and look for a long string that decodes twice. If you find one, every secret that workflow could read is exposed.

    echo 'PASTE-THE-STRING' | base64 -d | base64 -d
  4. Rotate those secrets, then download the affected logs for your records and delete the runs, so the secrets stop being readable.

  5. Find actions pinned by tag rather than by commit. Pin them to a full commit hash, as GitHub recommends.

    grep -rnE "uses: *[^ ]+@(v[0-9][^ ]*|main|master) *$" .github/workflows
  6. If a token you use on your Mac was stored as a repository secret, revoke it under Settings › Developer settings › Personal access tokens, and check which account the GitHub CLI is signed in with.

    gh auth status

Sources

Checked on 1 November 2026.

See what your agents and apps connect to

Kilobat is a Mac menu-bar app that names the app, process and agent behind every connection, keeps them away from known-bad hosts, and logs what each agent session reached.

Download for Mac ↓
brew install seifip/kilobat/kilobat