How to Secure GitHub Actions Against Supply Chain Attacks
Problem
I woke up one morning to find my CI/CD logs exposing secrets that should never have been visible. The tj-actions/changed-files action I had trusted for years was compromised.
# What I saw in my build logs2025-03-15T08:23:45.123Z ##[warning]Unexpected output detected2025-03-15T08:23:45.456Z AWS_ACCESS_KEY=AKIA...2025-03-15T08:23:45.789NPM_TOKEN=npm_...This was the tj-actions supply chain attack (CVE-2025-30066). Attackers compromised a maintainer account and injected malicious code that dumped CI/CD secrets into build logs. Over 23,000 repositories were affected.
The root cause? I was using mutable version tags instead of immutable commit SHAs.
What Happened
GitHub Actions supports three ways to reference an action:
1. Branch reference (MOST VULNERABLE):
- uses: actions/checkout@main # Points to latest commit on mainThis points to whatever commit is at the branch tip. Any new commit changes what code runs in my workflow.
2. Tag reference (STILL VULNERABLE):
- uses: tj-actions/changed-files@v41 # Tag can be movedTags can be moved, deleted, or force-pushed. Attackers who gain maintainer access can retag malicious code. This is exactly what happened in the tj-actions attack.
3. Commit SHA (MOST SECURE):
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # ImmutableThis is an immutable cryptographic reference. It cannot be changed without detection.
The attackers moved existing version tags (v41, v42, etc.) to point to malicious commits. Any workflow using tj-actions/changed-files@v41 automatically received the malicious code.
Why This Matters
The irony struck me hard. The attackers made their malicious releases immutable, something the legitimate maintainers had failed to do.
From Reddit discussion:"Trivy is a reputable provider, but they fucked up. All the more ironic that Trivy are a security vendor..."
"What's additionally ironic is that the attacker made their compromised tags/releases immutable, something that Trivy should have done"This attack showed that:
- Even security vendors can have weak release processes
- Reputation means nothing without verifiable security controls
- Mutable references are a single point of failure
- Transitive dependencies multiply the risk
The Fix: Pin to Commit SHA
I needed to replace all my tag references with commit SHAs. Here is the vulnerable workflow I had:
name: CIon: [push]
jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # VULNERABLE - uses: actions/setup-node@v4 # VULNERABLE with: node-version: '20' - uses: tj-actions/changed-files@v41 # COMPROMISED id: changed-files - run: npm ci - run: npm testI converted it to this secure version:
name: CIon: [push]
permissions: contents: read # Principle of least privilege
jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 with: node-version: '20' - uses: tj-actions/changed-files@d6e91a2266cdb9d620100ce78a7624c1e4a4178c # v41.0.1 id: changed-files - run: npm ci - run: npm testNotice I added the version as a comment after the SHA. This keeps the code readable while maintaining security.
How to Find Commit SHAs
I use three methods to find the correct SHA:
Method 1: GitHub API
curl -s https://api.github.com/repos/actions/checkout/tags | jq -r '.[] | select(.name == "v4.2.2") | .commit.sha'Method 2: Git ls-remote
git ls-remote https://github.com/actions/checkout refs/tags/v4.2.2Method 3: GitHub Releases Page
Navigate to the releases page of the action repository. The commit SHA is shown in the release details.
Set Up Dependabot for Updates
I do not want to manually track updates. Dependabot can automate this:
version: 2updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" open-pull-requests-limit: 5 reviewers: - "your-security-team"This creates pull requests when actions update. I can review the changes before merging.
Restrict Workflow Permissions
Even with SHA pinning, I limit what actions can do:
name: Deployon: push: branches: [main]
# Default to read-only for all jobspermissions: read-all
jobs: deploy: runs-on: ubuntu-latest # Grant write permissions only where needed permissions: contents: write id-token: write # For OIDC authentication steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Deploy run: ./deploy.shThis follows the principle of least privilege. If an action is compromised, the damage is limited.
Common Mistakes to Avoid
1. Assuming βVerifiedβ Actions Are Safe
Verification only confirms the publisher identity. The tj-actions publisher was legitimate; their account was compromised.
2. Using Branch References for Third-Party Actions
- uses: some-org/some-action@main # NEVER do thisBranch references provide zero protection against supply chain attacks.
3. Ignoring Transitive Dependencies
An action I use may depend on other actions. I check the action.yml file and any composite actions it references.
4. Not Auditing Release Processes
Before adopting any action, I now:
- Check the
.github/workflowsdirectory - Verify they use SHA pinning themselves
- Look for OIDC-based releases with signed artifacts
- Review their security policy
5. Granting Excessive Permissions
jobs: build: runs-on: ubuntu-latest # Implicitly gets write permissions!Instead, I explicitly declare minimal permissions:
permissions: contents: read
jobs: build: runs-on: ubuntu-latestSummary
The tj-actions attack and Trivy incident proved that supply chain attacks target the publishing infrastructure, not just the code. The single most effective protection is pinning all GitHub Actions to immutable commit SHA hashes.
Key actions I took:
- Audited all workflow files for tag references
- Replaced all
@v*references with full commit SHAs - Configured Dependabot for automated, reviewed updates
- Restricted workflow permissions to minimum required
- Started auditing action release processes before adoption
The attackers made their malicious releases immutable. The legitimate maintainers did not. That irony should be a wake-up call for every developer using GitHub Actions.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- π¨βπ» GitHub Security Hardening Documentation
- π¨βπ» CVE-2025-30066 Details
- π¨βπ» Palo Alto Unit 42 tj-actions Analysis
- π¨βπ» GitHub Actions Security Best Practices
Oh, and if you found these resources useful, donβt forget to support me by starring the repo on GitHub!
Comments