A familiar version tag pointed at different code
In March 2025, the tj-actions/changed-files GitHub Action was compromised. StepSecurity reported detecting unexpected outbound behaviour and investigating code that exposed CI/CD secrets in workflow logs. GitHub's advisory, CVE-2025-30066, records a malicious period on 14–15 March and explains that multiple version tags were redirected to malicious code.
The advisory lists action releases through 45.0.7 as affected and 46.0.1 as patched. These are the historical advisory boundaries. They do not establish that every repository using the action executed it during the malicious window, possessed a valuable secret or published a readable leaked log.
The build log became part of the exposure
A workflow runner often has authority that a source-code contributor does not: package publication, deployment, access to cloud services or signing operations. If code running in that job extracts secrets and prints them, the log is no longer a harmless diagnostic record. Its audience, retention and copies become incident-response questions.
The immediate fix is to stop executing the compromised dependency. The recovery task is broader: establish which jobs ran, what permissions and secrets they had, who could read the resulting output, and which credentials require revocation or rotation. A clean action revision cannot invalidate a credential already copied elsewhere.
| Record | What to collect | Decision it supports |
|---|---|---|
| Workflow execution | Run identity, timing and resolved action revision | Whether the suspect code actually ran |
| Job authority | Token permissions and available credentials | Potential downstream reach |
| Log exposure | Repository visibility and access to outputs | Who could have obtained printed material |
| Recovery | Revocation, rotation and downstream review | Whether exposed authority remains usable |
Pinning improves identity; review establishes trust
GitHub's secure-use guidance recommends pinning actions to a full-length commit SHA and checking that the commit belongs to the intended repository. A mutable tag is a convenient update label; it is weaker evidence of the exact code a workflow will execute.
Pinning does not independently establish that a chosen commit is safe. Review the action and its transitive execution, minimise job permissions, and avoid granting sensitive secrets to jobs that do not need them. Update pinned dependencies through a reviewable process so immutability does not become permanent abandonment of security maintenance.
FISRO recommends recording both the dependency revision and the job's authority in a release evidence package. Code identity without permissions misses the impact; permissions without identity miss the entry point.
Recover in the order authority can be reused
Identify affected executions first. Preserve the necessary forensic evidence with restricted access; avoid copying secret-bearing logs into tickets or public chat. Disable the suspect workflow path and follow the platform's current procedures for removing exposed output while retaining appropriate incident evidence.
Prioritise revocation and rotation using what each credential could do, not simply where it was stored. A deployment token, a package-publishing token and a short-lived read-only token create different downstream questions. Review relevant cloud, registry or package activity during the period in which an exposed credential could have been reused.
For persistent self-hosted runners, assess the runner environment separately. A repaired workflow file does not establish the state of a machine on which untrusted code previously executed. Follow the organisation's rebuild and incident-handling procedures rather than assuming the next clean job overwrote all earlier effects.
What xz teaches about the comparison
The xz case compromised release and build material; tj-actions redirected the code behind action tags. Both show why dependency trust must be tied to a specific artefact and execution route. Neither supports a claim that every open-source dependency is equally risky.
The connection to session theft is also operational: stopping the acquisition mechanism and invalidating the acquired authority are two different tasks. That separation should be visible in the incident checklist.
Locate use sites without executing a workflow
In an authorised local repository, search the workflow definitions for the affected action reference:
rg -n 'tj-actions/changed-files@' .github/workflows
The command reads files. It does not resolve the historical tag, execute the action or inspect workflows in other repositories. Include reusable workflows and organisation-level templates in the inventory through their own authorised access paths.
A current pinned commit or cleaned tag is not evidence of what a past run resolved. Preserve the run's recorded revision, timing, job permissions and relevant dependency metadata. If the available records do not establish the resolved code, retain that limitation instead of quietly substituting today's tag target.
A recovery sequence for exposed job authority
First identify executions during the malicious window using the advisory and available platform evidence. Then establish which secrets and token permissions the corresponding jobs could access. Public logs create a different disclosure audience from restricted private logs, but neither makes executed secret-reading code harmless.
Prioritise reusable credentials by the actions they permit and the period they remained usable. A package-publishing token warrants a publication-history review; a deployment identity warrants a review of relevant deployment and cloud activity. Removing a secret-bearing log limits continued exposure through that copy, but does not invalidate a value already obtained elsewhere.
For a persistent self-hosted runner, assess machine state separately. A new clean workflow can execute on an environment altered by the earlier job. Retain the runner identity and follow the organisation's trusted-rebuild process where compromise is supported or cannot be acceptably excluded.
Pinning needs an update and review policy
A full commit SHA gives a stronger code identity than a mutable version tag. It does not prove the selected commit is benign or that its transitive commands cannot fetch other code. Review the action's actual execution and the permissions of the job consuming it.
Keep a reviewable update route for pinned actions. Otherwise a control introduced to make code identity stable can become a reason never to adopt later security corrections. The responsible team should be able to identify the chosen revision, why it was accepted and how changes are evaluated.
Three distinct supply-chain boundaries
3CX's 2023 investigation traced compromise into a vendor's build environment. xz in March 2024 put generated release material and build conditions under scrutiny. Shai-Hulud in September 2025 added propagation through publishing credentials available during dependency installation.
For tj-actions, the specific boundary was execution of action code selected through mutable tags, followed by exposure of the job's authority in logs. That determines the evidence to preserve: resolved code, execution window, accessible credentials and log audience. A package adoption count cannot substitute for those execution records.
The practical closure test is that the vulnerable execution path is removed, exposed authority is invalidated or otherwise resolved, the relevant downstream activity has been reviewed and the runner environment has an acceptable trust basis. Each part should have an owner and evidence, so the last green workflow run does not conceal the remaining response work.
