Compromise once, deploy everywhere. The multiplier effect is what makes SSC attacks devastating.
🐴 XZ Utils (2024)
What Happened:
A contributor "Jia Tan" spent 2+ years gaining maintainer trust on the xz compression library, then injected a sophisticated backdoor targeting OpenSSH authentication. The backdoor would have compromised virtually every Linux server. Caught by Andres Freund who noticed a 500ms SSH login slowdown.
Key Lesson:
Trust is the ultimate attack vector. Social engineering over years can bypass every technical control. Open source maintainer burnout creates opportunities for patient attackers. Verify contributors, not just code.
⚡ tj-actions/changed-files (2025)
What Happened:
Compromised maintainer bot token, manipulated git tags to point to malicious commits. 23,000+ repos using @latest tags automatically pulled and executed malicious code that stole their CI secrets.
Key Lesson:
Never use floating tags (@latest, @main, @v1) in production. Always pin GitHub Actions to specific commit SHAs. Floating tags = uncontrolled automatic updates = potential automatic compromise.
🎭 S1ngularity - Nx (2025)
What Happened:
Exploited pull_request_target injection to steal npm token, published malicious Nx packages, which were then auto-installed by AI coding tools (Cursor, Cline), exfiltrating secrets from thousands of developer machines and CI environments.
Key Lesson:
pull_request_target gives untrusted code write access - extremely dangerous. AI tools amplify attack scale by auto-installing packages. One vulnerable workflow + package registry access = mass compromise at machine speed.
🪱 Shai-Hulud 2.0 (2025)
What Happened:
Massive npm supply-chain campaign compromising 700+ packages from major projects (Zapier, ENS Domains, PostHog, Postman). Preinstall malware executed during npm install, exfiltrating secrets and tokens across dev machines and CI/CD pipelines. Attack spread rapidly at ~1,000 new repos every 30 minutes, affecting 25,000+ GitHub repositories across ~500 users. Stolen data was cross-published, meaning victim credentials appeared in unaffiliated GitHub accounts.
Key Lesson:
Preinstall hooks can execute before your code even runs - widening attack surface to both dev and CI/CD environments. The scale and automation of modern supply chain attacks is unprecedented. With 27% of cloud environments potentially exposed, even popular, trusted packages can be weaponized. Immediate investigation required for npm-based environments.
Meta-lesson across all four:
Supply chain attacks exploit trust and automation. The things that make development fast (auto-updates, trusted dependencies, CI/CD automation) become attack vectors when compromised. Defense requires: pin versions, minimize trust, verify everything, and monitor the entire dependency chain.
Walk through all 4 briefly. Don't deep-dive — you revisit tj-actions, S1ngularity, and Shai-Hulud later. Transition: "These attacks all targeted CI/CD."
Quick level-set. Most audience knows Actions — just framing.
Jobs = isolation boundary. Steps share runner environment — that matters for security.
Walk through 4 elements. Highlight: checkout@v5 is a trust decision — a third-party action running in your context.
LEGO bricks: building blocks. Key point: actions run IN your job context — they get your secrets, tokens, and runner.
Quick scan of the grid. Actions touch everything. Transition: "So what happens when they get compromised?"
==================== PART 1: WEAPONIZE ====================
Transition slide — pause briefly, advance.
Three reasons: source code access, package publishing, secrets. Pipelines are the nexus of all three.
Ask: "What's wrong here?" Uses @v45 — a mutable tag.
Even pinned to @v45.0.7 it's still a tag, not a SHA. Tags can be moved — exactly what happened in the tj-actions attack.
Compromised bot PAT → tag overwritten → 23k repos auto-pulled malicious code. If you control the tag, you control downstream.
Solution: pin to SHA, enable Dependabot for automated updates, verify integrity. Key takeaway for audience.
Trust framework: tag vs SHA vs fork. SHA pinning is the default. Use OSSF Scorecard for third-party assessment.
Ask the audience: spot the vulnerability. Answer: user input in run command → script injection.
Four attack vectors from one injection: reverse shell, secret theft, code manipulation, infra compromise. Powerful demo moment.
Script injection was the entry point for Shai-Hulud. Secrets stolen, code compromised, infra breached.
All event fields shown are attacker-controlled. Never use directly in run commands.
Fix: assign to env var first, then reference as shell variable. Prevents expression injection.
Same workflow, now safe. The DISCUSSION_BODY env var is set at shell level — no expression expansion in the run line.
Ask: what happens on pull_request_target? Secrets exposed + untrusted code checkout + injection. Triple threat.
Spot three issues: pull_request_target + checkout of PR head + secrets exposed + injection in run. This is a triple threat.
1000s of secrets, 480 users' private repos, 500 repos from one company. Massive real impact.
pull_request_target runs in trusted context with secrets. Checking out PR head = running untrusted code with full access. GitHub's Dec 2025 fix helps but doesn't eliminate risk.
Caches persist across runs. Attacker poisons cache on a PR, subsequent builds on main pick it up. Never trust cache in release pipelines.
Default GITHUB_TOKEN has write-all permissions. If compromised, attacker gets full repo access. Always set permissions explicitly.
Set permissions: {} at workflow level. Add specific permissions per job. Failed is better than compromised.
Long-lived AWS keys in secrets: if leaked, attacker has permanent access. These never expire unless rotated.
OIDC: short-lived tokens, no stored secrets, cloud provider validates claims. Much safer than long-lived keys.
Five best practices: environments for gating, env-level secrets, restrict OIDC claims, rotate regularly, audit access. Tight claim conditions are critical.
Walk through the diagram: script injection → secrets stolen → action takeover → downstream compromise. Everything we discussed in one kill chain.
Real numbers: 218 orgs, 23k repos, 33k secrets. This is the scale of a single supply chain attack.
Transition slide — pause. "Now that we've seen the attacks, let's harden."
Quick recap of the four attack patterns we covered. Each has a clear mitigation.
GitHub org settings: allow-list actions, enforce SHA pinning, drop default token to read-only. Quick wins.