The GitHub Breach Wasn't a Fluke, It Was a Preview of What's Coming for All of Us

Last week, GitHub made a disclosure that most companies quietly dread: they had been breached. What made it unusual wasn't the breach itself, it was that GitHub told the world about it almost immediately, posting publicly before most companies would have even finished their internal incident triage. That transparency deserves recognition. But the breach itself deserves a hard look, because nothing about it was unique to GitHub. The same attack vector has been in active use for nearly a year, and most organizations are still not adequately prepared to defend against it.
What Happened
The entry point was a malicious VS Code extension: a trojanized Nx Console v18.95.0, published by nrwl.angular-console, a tool widely used in the JavaScript ecosystem.
The malicious version was registered on the Visual Studio Marketplace at 12:30 UTC on May 18, 2026. A maintainer received the routine publisher-notification email at 12:36 UTC, recognized that no release was planned, and unpublished at 12:47 UTC. The Open VSX copy was live for 36 minutes, until 13:09 UTC.
The window was enough for TeamPCP, the threat actor group responsible, to fully compromise GitHub, ultimately resulting in the exfiltration of approximately 3,800 GitHub repositories. (Nx postmortem).
The mechanism is elegant and alarming in equal measure. In the latest incident, the payload was a 2.3 MB obfuscated Python harvester (cat.py) with a macOS LaunchAgent for persistence. It targets the credentials that matter: Vault tokens, npm tokens, AWS IMDS and Secrets Manager and SSM, GitHub tokens (ghp_, gho_, ghs_), 1Password CLI sessions, SSH keys, .env files, GCP application-default credentials, Docker config. Exfiltration goes out over HTTPS, the GitHub API, and DNS covert channels.
A related incident from August 2025, attributed to the same threat actor group, used a different payload pattern on the Nx CLI npm package: a post-install script that invoked locally-installed AI CLIs (Claude, Gemini, Q) to enumerate filesystems and find credentials. The instructions to those local models were specific: recursively search local paths, find cryptocurrency wallet files, locate SSH keys and API tokens, avoid sudo, avoid anything that would trip EDR. The collected data was double-encoded and pushed to a GitHub repository the attacker controlled.
The pattern across both incidents is the same. A developer's laptop is loaded with credentials. The malicious code does not need privilege escalation. It reads files and makes network calls. Both incidents looked like a developer running their tools.
This is not a theoretical scenario. When we ran post-mortems on an earlier incident involving the same attack pattern (September 2025, same extension, same threat actor group), our own
EDR solution did not catch the attack for several hours. Why? Because it looked like a human. No sudo. No anomalous process spawning. Just a script reading files and making network calls. Exactly what many developer tools do legitimately.
This Is Not a GitHub Problem
GitHub is a well-funded, security-focused organization with dedicated teams whose sole job is to protect their infrastructure. If this happened to them, it can happen to anyone.
The threat actor group behind this, TeamPCP, publicly confirmed by multiple security researchers and GitHub itself, has been running the same playbook for nine to ten months. They are executing the same pattern over and over, and it keeps working because companies haven't made the changes necessary to stop it.
There are three reasons this keeps succeeding.
For years, developer machines have accumulated credentials like SSH keys, GitHub tokens, and sometimes even production access. We've told ourselves developers need all of this to do their jobs. The truth is most don't. Least privilege is achievable without killing productivity, but it's harder than handing out broad access, so security teams have largely left this alone. The risk was low, the tradeoff felt fine. Supply chain attacks have flipped that calculus. What was a tolerable shortcut is now the soft underbelly of every engineering organization. A single compromised developer laptop is often a full set of keys to the kingdom, repository access, pipeline access, sometimes even prod.
Second, nobody has good visibility into what's on those machines. When a developer is exploring a new feature or debugging something, they might install a dozen packages locally that never make it into the codebase. No SAST tool, no CSPM sees what's happening on the local machine before code gets committed. That pre-commit space is a black box for most security teams.
Cooldown is one of the right defenses for this class of attack, and the ecosystem has only shipped it halfway. pnpm added minimumReleaseAge in 10.16, npm followed with min-release-age in 11.10.0, and Yarn, Bun, Deno, and uv all have variants now. The VS Code Marketplace and Open VSX have nothing equivalent, and the two open feature requests on microsoft/vscode have not shipped. The threat actor used both gaps in sequence: the contributor's repo had minimum-release-age=10080 configured but the pinned pnpm version predated the feature and silently no-op'd it, and the extension marketplaces had no cooldown control to bypass in the first place. The other half of the defense is pulling from trusted internal sources before reaching the public registry. An internal proxy or mirror lets you scan, vet, and version-pin packages before they touch a developer machine, so even a malicious release that slips past cooldown still has to clear an inspection layer you control. Thefundamental logic of the attack does not change as the threat surface shifts from npm to extensions to whatever is next, so both controls need to travel with it.
What You Should Do Right Now
Here are the controls that came out of our discussions, in rough order of immediacy:
1. Disable auto-updates for VS Code extensions, today.
This is the simplest and most immediate thing you can do. The attack window exists because extensions auto-update silently. Turning off auto-updates eliminates the sub-20-minute attack window as a viable surface. Yes, developers will need to manually update extensions. That is a reasonable trade-off.
2. Push this as policy via your endpoint management tool.
Disabling auto-updates only works if developers actually comply. Use whatever endpoint management or MDM solution your organization runs (Jamf, Intune, etc.) to enforce this at the policy level so that the configuration cannot be modified by the end user. This is where the control actually sticks. If possible, restrict marketplace installations to a pre-approved safe allowlist.
3. Advocate for a cooldown period in VS Code.
NPM now supports cooldown periods, a configurable delay before pulling the latest version of a package. This buys defenders time: if an extension is compromised, detected, and pulled within 24–48 hours (which threat intel services are now achieving), then a 3-day cooldown means developers never install the malicious version. VS Code does not have this feature. Microsoft has, as of this writing, explicitly declined to build it. This needs to change. If you use VS Code, go upvote this feature request. Post about it. Make noise. This one feature would have prevented the GitHub incident.
4. Subscribe to a malicious-package threat intel feed.
Open Source Malware, Socket, and Step Security all publish feeds that surface malicious packages within minutes of disclosure. Open Source Malware offers a free RSS feed. Route it into your Slack. You will know before most of the internet does. Premium tiers provide direct integrations and faster signal.
5. Gain visibility into developer machines with an OSQuery-based tool.
The "before code" visibility gap is real. When the attack happens, you need to be able to answer: which developer machines have this extension installed right now? Which packages
are running locally? Tools built on osquery can answer these questions within seconds. This is an area where purpose-built security solutions are sparse; most tools in this space come from the IT/MDM world and are not designed with security queries in mind. But the capability exists, and it is worth investing in.
6. Consider a Library Firewall.
Library Firewalls deploy transparently via MDM, wrap package managers like npm, uv, pip, yarn and pnpm, and intercept installs that match known-bad signatures or behavioral signals. Several vendors operate in this space, including Socket and StepSecurity, with adjacent capabilities from Snyk, Endor Labs, and others. This category does not solve the VS Code extension problem, but it closes the broader supply chain attack surface that the same threat actor uses for npm-based campaigns.
7. Automate your incident response to supply chain signals.
When a threat intel signal comes in, speed matters. Build, or use your existing ASPM platform's ( e.g. ArmorCode, Kondukto etc) API, to automatically query your environment when a signal arrives: which of our packages matches this? Which endpoints are affected? Which repos reference this dependency?
With the right tooling, this can be a fully automated Slack workflow:
A new IOC lands → query executes → affected teams are notified → within minutes.
We have done this, and it has worked.
8. Verify your minimum-release-age controls are actually running.
If your repos use minimum-release-age (npm 11.5.1+, pnpm 10.16+), audit the packageManager field across your monorepos. A version pinned below the feature's introduction silently no-ops the control. Add a CI guard that fails the build if the runtime package manager is below the threshold. The same principle applies to any "defense by config key." Verify the runtime honors it.
The Bigger Picture
What this incident illustrates is that the developer environment is now the primary attack surface for sophisticated supply chain attacks. Attackers have figured out that the path of least
resistance into an organization runs straight through the developer's laptop, not the perimeter, not the cloud environment, not the application itself. The laptop.
The controls above are not exotic. They are achievable. But they require security teams to fight for them, because every one of them will encounter resistance from developers who do not want friction added to their tools. That fight is worth having. The alternative, watching TeamPCP execute the same playbook again in another six months, this time against your organization, is considerably worse.
The exposure window for Nx Console v18.95.0 was minutes, not hours, and on two different marketplaces. This was enough to compromise GitHub and exfiltrate 3,800 repositories is not going to get longer. As threat intel gets faster and detection improves, attackers will compress their timelines. The answer is not to match their speed; it is to remove the attack surface entirely. Start with auto-updates.
This blog reflects insights shared under Chatham House Rules by members of the PBC AI Center of Excellence. Individual organizations are not identified.


