In Part 1 of this blog series, we learned about GitHub Actions and their risks—now comes the fun part.
It’s time to see how those risks were actually exploited in the wild through real-world attacks from the past.
And we’ll finish with practical hardening techniques you can implement right now to secure your GitHub Actions.
Real world attacks
Example #1: The tj-actions supply chain compromise
In March 2025, a malicious update slipped into one of the most widely used GitHub Actions: tj-actions/changed-files.
This action helps developers detect which files changed in a pull request—it’s simple, harmless, and embedded in over 23,000 repositories. That’s what made it such an effective target.
When attackers compromised it, their malicious code spread instantly across thousands of trusted workflows, leaking secrets and compromising downstream pipelines.
GitHub, security vendors, and maintainers across the ecosystem had to scramble to revoke tokens, rotate keys, and rebuild clean workflows.
The most interesting part? It wasn’t a zero-day. It was a chain reaction of four ordinary security oversights—each small on its own, but devastating when combined.
The attack in five steps

1. Workflow injection via pull_request_target (spotbugs/sonar-findbugs)
It began when attackers exploited a repository using the risky pull_request_target event.
That trigger runs code from untrusted forks with access to repository secrets—a perfect entry point.
They used it to extract credentials from a SpotBugs maintainer who had access to multiple projects.
2. Supply chain compromise (reviewdog/action-setup)
Among the secrets stolen was a key belonging to a Reviewdog maintainer.
With that access, the attackers backdoored the Reviewdog setup action, inserting malicious code into a dependency used by other projects—including tj-actions.
This is the moment the incident moved from a simple repo compromise to a supply-chain attack.
3. Secrets management failure (tj-actions-bot)
When the compromised Reviewdog action executed inside tj-actions, it captured an overprivileged bot token—the tj-actions-bot PAT.
The token had broad write permissions and no scope restrictions.
That single secret gave the attackers full control over the tj-actions repository.
4. Dependency pinning failure (tj-actions/changed-files@v35)
Armed with repository access, the attackers modified version tags like v35 and v45 to point to a malicious commit.
Because most users referenced version tags instead of immutable commit SHAs, tens of thousands of workflows unknowingly pulled the attacker’s code.
This step turned one compromised repository into a platform-wide supply chain breach.
5. Secrets exposure (memdump.py)
Once executed, the malicious code scanned the GitHub runner’s memory for sensitive data—AWS keys, personal access tokens, npm tokens, and private keys.
It then dumped them into the workflow logs, exposing them to anyone with access to the repository.
Public repositories made this exposure visible to the world. While 23,000+ repositories used the action, approximately 218 actually exposed secrets during the compromise window.
Key takeaway
This wasn’t the result of one sophisticated exploit—it was four ordinary misconfigurations colliding: weak event triggers, excessive permissions, mutable dependencies, and poor runtime isolation.
If any one of these controls had been enforced—least-privilege tokens, SHA pinning, secret scanning, or ephemeral runners—the attack chain would have broken.
That’s the definition of defense in depth for GitHub Actions.
Example #2: CVE-2020-15228: when a string becomes a command
This vulnerability affected the core GitHub Actions runner itself—meaning every workflow on the platform was potentially exploitable.
Discovered by Felix Wilhelm from Google Project Zero, this was a simple yet brilliant demonstration of how “harmless text” can take over your entire CI/CD pipeline.
GitHub Actions runners used to interpret certain strings printed to STDOUT as workflow commands—things like ::set-env or ::add-path. If your workflow echoed user-controlled input (like a PR title or issue body), an attacker could sneak in those command sequences and change environment variables or your system PATH.
Example: An attacker opens a PR titled: Fix typo ::set-env name=PATH::/tmp/evil
With this GitHub Action:


When the workflow echoes the PR title, the runner sees set-env and actually executes it, setting the PATH to the attacker’s folder. Now, when npm test runs—it doesn’t use your npm. It runs theirs. Game over. Full control of your build.
GitHub deprecated these commands and introduced the safer, file-based GITHUB_ENV approach—but the pattern remains. Anywhere you evaluate user-controlled data (${{ }} expressions, run commands, composite actions), you risk injecting code.
Lesson: never trust user input in evaluated contexts. PR titles, issue text, branch names—all are untrusted data.
Example #3: PyTorch – when a typo fix becomes a backdoor
PyTorch is one of the world’s most popular machine learning frameworks, used by Google, Meta, and millions of developers—compromising it would poison the entire AI supply chain.
This one’s almost poetic.
Researchers John Stawinski IV and Adnan Khan submitted a one-line typo fix to PyTorch—just a harmless README change. That small contribution made them “trusted contributors,” meaning their next pull requests could run workflows without manual approval.

They then pushed a malicious workflow that was executed on PyTorch’s self-hosted runners—servers with persistent state and powerful permissions. The workflow accessed active GITHUB_TOKENs with write permissions and could modify repositories, upload releases, or extract secrets.
Because the runners weren’t ephemeral, the attackers could stay hidden for as long as they wanted—no need for repeated PRs or suspicious commits. They essentially camped on the infrastructure, silently harvesting tokens and maintaining stealth.
Root cause: overprivileged tokens and persistent self-hosted runners.
Lesson: it’s not always about technical sophistication. Sometimes it’s just about abusing trust and persistence.
Security hardening for GitHub Actions
So… the best hardening you can do is disconnect all the cables and throw your computer in the trash.
Yeah, it’s an overused dad joke—but there’s truth in it.
Since that’s not realistic, here’s what you can actually do:
Pin actions to commit SHAs
Always pin to commit SHAs, not version tags.
We saw how attackers redirected tags in the tj-actions incident. SHAs are immutable—tags aren’t.
Use least privilege and environment-specific secrets
Set permissions to read-only by default, and grant write only when necessary.
Use environment-specific secrets so production keys require manual approval.
Use verified creators (but stay skeptical)
Verified creators add trust, but not immunity.
Even verified publishers can get compromised—or their accounts taken over.
Never trust user-controlled data
PR titles, issue descriptions, branch names—all can be weaponized.
Treat every piece of user input as potentially malicious.
Use environment variables for injection prevention
When you need to handle user data safely, use environment variables instead of direct shell interpolation.
This prevents injection into workflow commands like ::set-env.
Secure self-hosted runners
- Never use self-hosted runners for public repositories.
- Use ephemeral runners that auto-destroy after each job.
- Require approval for all outside-collaborator workflows.
- Set GITHUB_TOKEN to read-only by default.
- Apply network isolation—no internal access from build environments.
Protect workflow files with CODEOWNERS
Require security team approval for any change under .github/workflows/.
This prevents silent modifications of your pipelines.

Validate artifacts and logs
Validate uploaded artifacts, scan for malicious content, and make sure logs don’t leak sensitive data.
Artifacts can contain secrets—always scan before release.
Useful tools
Some great open-source tools to help you stay ahead:
- Zizmor – purpose-built for GitHub Actions, with low false positives.
- snyk-labs/github-actions-scanner – solid general checks.
- ActSpect (Axonius) – advanced supply-chain analysis of GitHub Actions.
- Orca Security – continuous cloud and CI/CD security, with a SAST engine that lets you define custom policies (yes, that’s us).
Closing thoughts
All of these real world examples show the same truth: CI/CD pipelines aren’t just automation—they’re production systems with the same attack surface as your code.
Defense in depth isn’t a slogan here—it’s survival.
Pin your dependencies, reduce your permissions, isolate your runners, and treat every workflow like code that an attacker might read, modify, or exploit.
About the Orca Cloud Security Platform
Orca offers a unified and comprehensive cloud security platform that identifies, prioritizes, and remediates security risks and compliance issues across AWS, Azure, Google Cloud, Oracle Cloud, Alibaba Cloud, and Kubernetes. The Orca Cloud Security Platform leverages Orca’s patented SideScanning™ technology to provide complete coverage and comprehensive risk detection.
Interested in discovering the benefits of the Orca Cloud Security Platform and its Application Security (AppSec) capabilities? Schedule a personalized 1:1 demo.
