GitHub got popped. Grafana too. Here's the playbook for everyone else.
TeamPCP spent May 2026 working through the security industry like a hit list. How to tell if you're already infected, what to do if you are, and what hardens you for the next wave.
On May 18, GitHub got popped.
On May 18, GitHub got popped. An employee installed a poisoned VS Code extension. Within hours, 3,800 of GitHub's internal repositories were sitting on a forum priced at $50,000. (GitHub's writeup)
The day before, Grafana Labs detected malicious activity in their CI/CD. The TanStack npm packages they pulled had been backdoored upstream. Grafana rotated their GitHub workflow tokens fast. They missed one. The attackers used the missed token to download Grafana's codebase, then asked for a ransom. Grafana refused. (Grafana's writeup)
The day after GitHub got popped, three malicious versions of Microsoft's own durabletask Python package landed on PyPI in a 35-minute window. Six hundred and thirty-nine poisoned versions of AntV's npm namespace went up the same day. Same crew that did GitHub.
The crew is TeamPCP, tracked by Google Threat Intelligence as UNC6780. Their tool is a self-replicating worm called Mini Shai-Hulud. Steal CI tokens from a maintainer. Republish poisoned packages. Those packages run in someone else's CI. Steal those tokens. Publish more poisoned packages. The worm finds new hosts every hour.
If GitHub, Grafana, Microsoft, Vercel, Bitwarden, Trivy, and Checkmarx all got rolled this spring, the builder shipping a side project on a Cursor-coded weekend doesn't stand a chance unless they know the playbook. The good news is the playbook is short. The better news is everyone who got hit published a postmortem.
The hit list
Each link goes to the public writeup from the affected company. Twenty minutes with these will teach you more than a year of CVE feed.
Vercel via Context.ai (Feb to Apr 2026). Infostealer on a third-party AI vendor's laptop became OAuth tokens, became a Vercel employee's Google Workspace, became customer env vars on the platform. 580 employee records, NPM tokens, partial source code, listed for $2M. (Vercel bulletin, Trend Micro deep dive)
Axios (March 31). Two poisoned versions of a package that does 70 million weekly downloads. RAT shipped to dev machines across macOS, Windows, and Linux. Microsoft attributed it to Sapphire Sleet (North Korea). (Microsoft writeup)
Unscoped `tanstack` brand-squat (April 29). Different actor, same playbook. Account sh20raj registered the unscoped tanstack name (vs. the legit @tanstack/* scoped packages) and pushed four versions (2.0.4 through 2.0.7) in a 27-minute window. Postinstall hook read every .env in the project and POSTed contents to a Svix webhook. AWS keys, GitHub tokens, npm tokens, database creds, OpenAI keys, Stripe keys, all drained while the package quietly installed. Not TeamPCP. (Socket writeup)
TanStack (May 11). Eighty-four malicious versions across 42 packages, signed through TanStack's own compromised CI/CD pipeline. So downstream filters waved them through. The infostealer harvested tokens for GitHub Actions, GitLab, CircleCI, AWS, GCP, Azure, Kubernetes, and HashiCorp Vault. (TanStack advisory)
Grafana Labs (May 17). Codebase exfiltrated. Public refusal of the ransom demand. The breach was one missed workflow token during mid-incident rotation.
Nx Console VS Code extension (May 18). 2.2 million install base. The bad version was live for eleven minutes. It scraped tokens for GitHub, npm, AWS, HashiCorp Vault, Kubernetes, 1Password, and specifically targeted ~/.claude/settings.json. Per Nx, more than 6,000 developers installed it. (Nx advisory)
GitHub internal (May 18). 3,800 repos through one employee's machine running a poisoned extension.
Microsoft `durabletask` on PyPI (May 19). Three poisoned versions in 35 minutes via a previously compromised maintainer account that still had publish access.
AntV npm namespace (May 19). 639 poisoned versions across 323 unique packages, including echarts-for-react (~1.1M weekly downloads). Maintainer account atool compromised. Same crew reused the @ git-dep injection trick as @antv/setup. (Socket writeup)
`parikhpreyash4` / `gvfsd-network` postinstall campaign (May 22). Different actor again. A single attacker pushed identical postinstall payloads into 700+ repos via direct upstream commits, hiding them in package.json files inside PHP Composer packages. The hook downloaded a binary from attacker GitHub Releases (gvfsd-network), dropped it to /tmp/.sshd, and ran it in the background. Highest-impact victims: devdojo/wave (Laravel SaaS starter kit, 6,400 stars) and devdojo/genesis (1,300 stars, 9,100 Packagist installs). Starter kits amplify this attack because the malicious package.json lands at project root when forked. Not TeamPCP. (Socket disclosure)
The transparency is the only thing actually working in 2026. Every one of these companies published a postmortem within days. That's free training data.
The pattern
A maintainer's laptop or a maintainer's CI gets owned. Their tokens get stolen. The tokens publish poisoned versions of every package they maintain. Those packages run in your CI. Your tokens get stolen. Your packages get poisoned. The next victim's CI runs them.
The reason it keeps working: maintainers are one or two-person operations. CI tokens are long-lived. Lockfiles are optional. Version tags are mutable. VS Code extensions get full access to every credential file on your machine. The system was never designed for this threat model.
How would you even know you got popped?
Most builders find out from someone else. OpenAI emails them about a leaked key. The cloud bill triples overnight. A friend asks why their npm package just shipped a new version they didn't write. Don't be that builder. Eight things to check, by hand or by agent:
1. Outbound from CI to weird places. GitHub Actions or your CI runner making outbound calls to gist.githubusercontent.com, ngrok.io, pipedream.com, or raw.githubusercontent.com for files you didn't fetch is exfiltration in progress. StepSecurity Harden-Runner catches this for free.
2. Base64 blobs in build logs. If an action's output suddenly contains long base64 strings, decode them twice (echo 'xxx' | base64 -d | base64 -d). That's how the March 2025 tj-actions incident first got caught.
3. Post-install scripts you didn't write. Run npm ls --all --long and look at every postinstall, preinstall, and install hook. If a dependency you've used for years suddenly has one, look at the upstream repo. If the commit doesn't match the registry, the maintainer is popped. Lifecycle scripts that POST to webhook services (Svix, ngrok, pipedream, hookbin) are exfiltration in progress. The unscoped tanstack brand-squat used exactly that pattern to drain .env files in 27 minutes.
4. CI run time or log size spikes. A 30-second build that's suddenly 90 seconds is doing more than it should. A 2KB log that's suddenly 200KB has something extra in it.
5. Token usage from unfamiliar IPs. GitHub: Settings → org Audit log. AWS: CloudTrail IAM events. Stripe: Developers → activity log. OpenAI: Settings → Usage. npm: npm token list.
6. Cloud bills jumping. Sysdig clocked leaked OpenAI keys getting used within four minutes. Anthropic, OpenAI, and AWS will all alert on anomalous usage if you turn it on. Turn it on.
7. New deploy keys, SSH keys, or OAuth apps. GitHub: Settings → Deploy keys per repo, SSH keys on your profile, OAuth apps, GitHub Apps. Anything you don't recognize, revoke immediately.
8. Local config tampering. After Nx Console, check ~/.claude/settings.json for changes you didn't make. Same for .cursor, .vscode/settings.json, ~/.aws/credentials, ~/.npmrc, ~/.gitconfig. Most builders never look at these. Worms know that.
Run a compromise check on this machine and repo. For each signal,
report status (OK / SUSPICIOUS / COMPROMISED) and recommended action.
1. Search GitHub Actions logs in the last 30 days for outbound calls to
gist.githubusercontent.com, ngrok.io, pipedream.com, or unknown
raw.githubusercontent.com paths.
2. Search every workflow log in the last 30 days for base64-encoded
strings over 100 chars. Decode them. Flag anything that decodes
to credentials or remote payloads.
3. Run `npm ls --all --long --json` and list every dependency with a
postinstall, preinstall, or install lifecycle script. Cross-check
the upstream repo to confirm the commit matches what's published.
4. Compare CI run times and log sizes for the last 30 days against
the prior 90. Flag any 2x or larger jumps.
5. Pull the GitHub org audit log. Flag PAT usage from IPs not seen
in the prior 90 days. Same for AWS CloudTrail IAM events and
Stripe Developers activity.
6. Pull the last 30 days of usage from OpenAI, Anthropic, AWS, and
any other API tied to billing. Flag 2x+ spikes against baseline.
7. List every active deploy key, SSH key, OAuth app, and GitHub App
on this account with the date added. Flag anything from the last
30 days I can't immediately explain.
8. Diff `~/.claude/settings.json`, `~/.cursor/`, `~/.vscode/settings.json`,
`~/.aws/credentials`, `~/.npmrc`, `~/.gitconfig` against their last
committed or last-known-good versions. Flag any added entries.
Format: one signal per line, status, recommended action.
What to do if the answer is yes
The instinct is to fix it quietly and hope. Don't. Grafana, GitHub, and Vercel all went public within days and the industry rallied around them. Quiet breaches get worse. Loud breaches get help.
1. Stop the bleeding. Pause all CI workflows. Revoke, don't rotate, the tokens that are in scope. Rotation lets the attacker watch you do it. Revocation kicks them.
2. Scope the blast radius. What tokens lived in the compromised path. What cloud roles those tokens had. What data those roles could touch. Customer data? Production database? Stripe? The answer to "what's the worst they could have done" is your scope.
3. Audit the logs, 90 days minimum. GitHub org audit log. AWS CloudTrail. npm publish history. Cloud provider activity. Every SaaS that holds a token. The attacker's timeline is in there.
4. Rotate everything in scope. This is the Grafana lesson. You will miss one. Then check that you didn't. Then have your agent check. Then check again.
5. Hunt for persistence. Deploy keys, IAM users, GitHub Apps, OAuth grants, scheduled workflows, cron jobs, new collaborators on private repos. Anything created in the affected window that isn't yours, delete it.
6. Notify. Affected users, your team, your providers (npm, GitHub, AWS), regulators if customer data was in scope, customers directly if their credentials were exposed. Be specific, not theatrical.
7. Clean rebuild. Fresh lockfile from a known-good state. New CI runners. Fresh creds everywhere. If you can't be sure your workstation is clean, reimage it. Yes, fully.
8. Publish the postmortem. Publicly. The transparency norm is the only thing actually working. The next builder you save might be you.
I think I've been compromised. Walk me through incident response.
Phase 1 - Contain (next 30 minutes):
- List every CI workflow currently enabled. Help me disable.
- List every active token (npm, GitHub PAT, deploy keys, AWS keys,
Stripe keys, every SaaS in my config). For each that had any path
to production, REVOKE (not rotate) immediately.
Phase 2 - Scope:
- For the suspected compromised path, list every token in scope.
- For each token, list what it had access to (cloud roles, repo
scopes, registry scopes).
- Tell me the worst-case data exposure given that scope.
Phase 3 - Log audit:
- Pull and summarize the GitHub org audit log, AWS CloudTrail, npm
publish history, and every other SaaS audit log I can authenticate
to. Last 90 days.
- Flag every event involving a token in the compromised scope.
Phase 4 - Rotate:
- Generate a rotation checklist for every token, key, secret, and
OAuth grant in the blast radius. Group by provider. Cross-reference
against audit findings so we don't miss one.
Phase 5 - Persistence hunt:
- List every deploy key, IAM user, GitHub App, OAuth app, scheduled
workflow, and cron job created in the affected window. Flag
anything I can't explain.
Phase 6 - Notify and document:
- Draft notification emails to my team, affected users, and providers
based on the actual scope.
- Draft a public postmortem with timeline, root cause, scope, and
what we changed. Use Grafana, Vercel, and GitHub May 2026 incident
reports as style references.
What hardens you against the next one
The companies that didn't get popped (or didn't get popped as badly) had four things in common. None of them require a security team.
Lockfile committed, frozen install, lifecycle scripts disabled. Every Mini Shai-Hulud victim got hit because CI pulled the poisoned version on the next install. Repos pinned to a clean lockfile that wasn't deleted were immune until they manually updated. Switch CI to npm ci, pnpm install --frozen-lockfile, or yarn install --immutable. For the single biggest postinstall defense, add --ignore-scripts to your install commands (or set ignore-scripts=true in .npmrc). Better yet, migrate to pnpm v10+, which disables dependency lifecycle scripts by default. That one config change neutralizes both the parikhpreyash4 campaign and the unscoped tanstack brand-squat.
Third-party GitHub Actions [pinned to commit SHA](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-third-party-actions), not tag. A version tag is a sticky note the maintainer can move. A SHA is a fingerprint nobody can fake. Renovate and Dependabot both keep SHA-pinned actions current via reviewable PRs.
Every VS Code and Cursor extension audited. This is the Nx Console lesson and the GitHub-internal lesson, in the same week. An extension can read every credential file on your machine. Treat installing one like installing root malware until proven otherwise. Unverified publishers, fewer than 1000 installs, or shipped a new version in the last 7 days are all elevated risk.
Continuous secret scanning, push protection on, audit logs reviewed weekly. Grafana missed one token in their rotation and that was the breach. gitleaks catches what you miss. GitHub Push Protection blocks the next leak before it leaves your laptop, free.
Set up hygiene baseline on this repo and machine.
1. Lockfile state: Confirm a lockfile is committed. Switch CI to
`npm ci` / `pnpm install --frozen-lockfile` / `yarn install --immutable`.
Show me the diff.
2. GitHub Actions pinning: Walk every .github/workflows/*.yml. For
each third-party `uses:` line, look up the full 40-char SHA for
the current tag and rewrite as `uses: owner/action@<sha> # vX.Y.Z`.
Generate a Renovate config with pinDigests.
3. Extension audit: List every VS Code and Cursor extension currently
installed. For each: publisher, install count, last update, current
version. Flag any with unverified publisher, fewer than 1000
installs, or updated within the last 7 days.
4. Secret scanning: Run `gitleaks detect --source . --log-opts="--all"
--report-format json`. For each finding: file, line, commit, secret
type, rotation procedure. Then turn on GitHub Secret Scanning Push
Protection.
5. Weekly recurring task: Pull the GitHub org audit log diff for the
last 7 days. Surface any new tokens, deploy keys, OAuth grants, or
workflow changes I didn't author.
Show me what you've changed before applying anything.
The next one is publishing right now.
The crew running this worm is good. They're publishing 84 malicious versions in a coordinated drop. They're getting in through OAuth tokens nobody flagged. They're harvesting credentials from VS Code extensions that have been live for 11 minutes.
What's working is the transparency. Read every postmortem the moment it lands. Pattern-match.
The next one is publishing right now.
Run Composed on your last 10 PRs.
Apply for design partner access and we’ll show which findings are real, exploitable, and worth fixing — free.
Run it on 10 PRs →