Back to Blog

How One VS Code Extension Took Down ~3,800 GitHub Internal Repos

How One VS Code Extension Took Down ~3,800 GitHub Internal Repos
TL;DR: GitHub's confirmation that a poisoned VS Code extension on an employee device exposed around 3,800 internal repositories is the loudest hit yet in a TeamPCP campaign that has been running since at least early May. Our read of the campaign points to a single technical chain: a .vscode/tasks.json autorun, an extension marketplace whose review process cannot keep up, and a C2 design (FIRESCALE) that survives any single domain takedown. The IDE is now the soft underbelly of the entire developer supply chain, and most security stacks were not built to see it.

Key facts

  • Date GitHub confirmed: May 20, 2026
  • Initial vector: Poisoned VS Code extension on a single employee device
  • Reported scope: Around 3,800 GitHub internal repositories
  • Threat actor: TeamPCP, tracked as UNC6780 by Google's threat intel group
  • Listed price for the data: $50,000 on a cybercrime forum
  • Adjacent activity by the same actor in May 2026: hundreds of compromised npm and PyPI packages tied to TanStack, Mistral AI, UiPath, and OpenSearch (Mini Shai-Hulud worm, hit on May 11, worm code published May 12), two OpenAI employee Macs impacted via the TanStack wave (OpenAI forced macOS app updates with a June 12, 2026 certificate cutoff), three malicious versions of Microsoft's durabletask PyPI package on May 19, and the Nx Console VS Code extension compromise on May 18 (nrwl.angular-console v18.95.0, ~2.2M installs, live for 11 minutes).
  • Customer impact reported by GitHub: None at this stage

Our read on what actually happened

GitHub has been compromised by TeamPCP. GitHub confirmed the internal breach this morning: a poisoned VS Code extension on an employee device, around 3,800 internal repositories exfiltrated. TeamPCP is already selling the data on a cybercrime forum for $50K. We have been following this group's campaign across npm, PyPI, and the VS Code Marketplace since May 11. Today's hit is the loudest yet, and it is the latest beat in a sequence that already pulled in TanStack, two OpenAI employee Macs, Microsoft's durabletask PyPI package, and the Nx Console VS Code extension in the prior nine days. Most coverage reads today's incident as a one-off employee-device compromise. From where we sit, that read undersells the problem by an order of magnitude.

The same actor ran a self-propagating worm (Mini Shai-Hulud) across hundreds of npm and PyPI packages tied to TanStack, Mistral AI, UiPath, and OpenSearch starting on May 11. They hit the Nx Console VS Code extension two days before GitHub confirmed its own incident, using stolen publisher credentials chained from an earlier supply chain compromise to push a malicious version (nrwl.angular-console v18.95.0, over two million existing installs) to the official VS Code Marketplace. That malicious version was live for eleven minutes. The GitHub breach is the same playbook one tier higher in the stack: get code on a developer's machine, harvest the credentials in that developer's scope, and use those credentials to reach the next tier.

The mechanism is mundane. The blast radius is the story.

How the attack chain runs

We have seen, across the broader campaign that the GitHub breach sits inside, is a consistent technical chain. The chain has four steps and almost no detection surface for traditional security stacks.

Step 1: The poisoned extension lands on a developer machine

The extension publishes to a trusted marketplace using either a typo-squatted name, a stolen publisher credential, or a legitimate package that was hijacked through an OIDC token exchange (the trick TeamPCP used to compromise TanStack's release workflow). The developer installs it the way they install anything else, or it ships as a transitive dependency through an extension pack.

Step 2: A .vscode/tasks.json file silently runs on folder open

The malicious payload, or a follow-up dropper, places a .vscode/tasks.json file inside a repository the developer is likely to open. The file sets "runOptions": { "runOn": "folderOpen" } on a task that executes a shell command. In VS Code with Workspace Trust enabled, this prompts. In Cursor, which ships with Workspace Trust turned off by default, it runs silently. Open the folder, the task fires.

Step 3: The stealer harvests everything in the developer's local context

The shell command pulls a payload from an attacker-controlled domain. Public analysis of TeamPCP payloads from the same campaign window describes a stealer that reads HashiCorp Vault KV secrets, unlocks 1Password and Bitwarden vaults, dumps SSH keys, exfiltrates Docker credentials, copies VPN configs, and pulls shell history. If the host is on AWS, it propagates to other EC2 instances using SSM SendCommand with the AWS-RunShellScript document. Inside Kubernetes, it uses kubectl exec to move between pods.

Step 4: Stolen credentials reach the next tier

Once the stealer has the developer's tokens, the campaign uses them to publish malicious package versions to npm and PyPI, push code to internal repos, or in GitHub's case, exfiltrate around 3,800 internal repositories. Every infected CI run becomes a new publisher. Every developer machine becomes a credential warehouse. Every credential warehouse becomes the next entry point.

That is how a worm built for the open-source ecosystem ends up walking out of GitHub's internal estate.

Why a one-line config in .vscode/tasks.json is the new soft underbelly

The setting that makes this attack chain work is two words long: "runOn": "folderOpen". It is documented, supported, and intended for legitimate developer workflows (run a watcher on open, start a dev server, kick off a linter).

The problem is the trust model around it. Workspace Trust in VS Code blocks autorun tasks until a developer explicitly grants trust to the folder. Cursor, the AI-native fork now installed across most engineering orgs we look at, ships with Workspace Trust off by default. That single configuration difference turns a documented feature into a one-click RCE on every open of an attacker-controlled folder.

The IDE has filesystem access, terminal access, network access, environment variables, auto-updating extensions, and the active session of every service holding a token on that machine. That trust boundary now lives behind a JSON file that almost no security tool inspects.

FIRESCALE: why takedowns do not end this campaign

The most under-discussed part of this campaign is how it survives the response phase. The TeamPCP toolkit uses a C2 design called FIRESCALE.

The primary C2 server has a hardcoded address. If that server is sinkholed or seized, the malware searches GitHub's public commit messages worldwide for the pattern FIRESCALE <base64_url>.<base64_signature>. The base64-encoded URL is only accepted if its RSA-SHA256 signature verifies against a 4096-bit public key embedded in the malware. Only the attacker, who holds the corresponding private key, can publish a valid new C2 address. A single public commit to any repository on GitHub re-points the entire worm.

Exfiltration follows three paths: primary C2, FIRESCALE dead-drop, and the victim's own GitHub account. Blocking one tier leaves the other two intact. Sinkhole orders do not end this campaign. As long as TeamPCP can publish a single signed commit anywhere on GitHub, the worm has a usable C2 channel.

Why this is an industry-level urgency

If you read the headlines as "GitHub got hit," the natural reaction is to feel sorry for them and move on. We do not read it that way.

In the past three weeks the same actor compromised the build system of the most popular React data-fetching library, took down two OpenAI employees' Macs through a downstream package, hijacked a 2.2-million-install VS Code extension with stolen publisher credentials, infected Microsoft's official durabletask PyPI package, and exfiltrated around 3,800 internal repositories from GitHub. Public reporting also links the same campaign to source code theft at Cisco.

Five household-name engineering organizations, plus CISA acknowledging the campaign without issuing a standalone advisory, all touched by the same playbook in three weeks. If your engineering team installs extensions, opens cloned repos, or runs CI in any major package ecosystem, you are inside the blast radius. The only variable left is whether you have visibility into the moments where the playbook would trigger on your stack.

What every security team should do this week

This is the part where the analysis turns into an operational checklist. None of these are optional if your developers use VS Code or Cursor.

  • Enforce Workspace Trust everywhere. Set security.workspace.trust.enabled to true in VS Code. In Cursor, push the equivalent setting through MDM or your endpoint config. Treat any opt-out as a security exception that needs approval.
  • Disable automatic task execution. Set task.allowAutomaticTasks to off in both VS Code and Cursor. A developer choosing to run a task is fine. The IDE choosing to run it on folder open is not.
  • Inventory installed extensions. Pull a list of installed VS Code and Cursor extensions across your fleet. Cross-reference against the Nx Console compromise (nrwl.angular-console v18.95.0) and any other extension version flagged in the campaign. Uninstall and reinstall from verified sources.
  • Treat .vscode/tasks.json as a security finding. Scan every internal repo and every cloned third-party repo for a tasks.json that sets runOn to folderOpen or folderOpened. Block at PR review for internal repos. Quarantine for third-party repos until reviewed.
  • Rotate any token that touched a developer machine running an unverified extension in the past 30 days. Yes, all of them. Yes, including the ones nobody remembers issuing.
  • Stand up time-to-revoke as a tracked metric. When a token is suspected of leaking, the time from suspicion to revocation should be measured in minutes. Anything that lands in the next-business-day bucket is a process failure worth fixing now.
  • Block the FIRESCALE pattern at egress. If your network detection can identify outbound calls to GitHub's search API followed by traffic to a brand new domain, flag it. That is the FIRESCALE handoff in motion.

What the industry needs to fix

The operational checklist above is necessary and not sufficient. The structural fixes have to land at the platform layer.

Marketplaces (VS Code, Cursor, JetBrains, npm, PyPI, Homebrew, GitHub Actions) need stronger publisher identity guarantees and binding between published artifacts and human-verifiable identities. SLSA Build Level 3 provenance is a step, but TeamPCP has shown that an attacker who can mint OIDC tokens from a hijacked CI run can produce cryptographically valid provenance for malware. Provenance without identity assurance is theater.

IDEs need a default-deny stance on autorun. A default-allow posture with a toggle most developers will not find is functionally the same as no protection. Cursor in particular needs to ship Workspace Trust on by default; the current configuration treats developer convenience as more important than developer safety, and TeamPCP is exploiting that exact tradeoff.

Security stacks need to treat the IDE as a first-class attack surface. Endpoint tools that scan ~/Documents and ignore ~/.cursor/extensions are blind to the actual perimeter. The IDE is where the credentials, the tokens, and the trust live.

How Cantina handles this

Cantina brings AppSec and OpSec and everything in between into one platform, which is what an incident shaped like this requires.

AppSec: IDE config as a first-class artifact

IDE configuration files, extension manifests, and dependency manifests inside every repo are scanned as first-class artifacts. A .vscode/tasks.json that sets runOn to folderOpen and shells out to a download gets surfaced as a finding the same way a hardcoded credential would. Extension manifests are checked against publisher identity, version history, and known-compromised lists.

OpSec: developer endpoints correlated with token usage

Developer endpoints are correlated with their identities, their token usage, and the repositories they touch. A token used from a new geography ten minutes after a developer opens a fresh folder is not a normal event, and it should not be treated as one. The signal that matters here is the correlation, not any one telemetry stream in isolation.

Time-to-revoke: from fire drill to controlled response

When a token does leak, the question stops being "did anything happen" and starts being "how fast can we revoke." Cantina maps every secret to every place it was used, so revocation is targeted rather than a guess. That is the same time-to-revoke discipline that turned GitHub's overnight rotation from a fire drill into a controlled response.

If your developers run VS Code, Cursor, or any other extension-based IDE, the GitHub incident is a preview of how your worst day starts.

Talk to us

Book a demo. We can walk through what Cantina sees on a developer machine in your stack.

Frequently asked questions

What happened in the GitHub breach on May 20, 2026?

GitHub confirmed that a poisoned VS Code extension on an employee device gave an unauthorized actor access to around 3,800 internal repositories. The malicious extension was removed, the device was isolated, and critical secrets were rotated overnight starting with the highest-impact credentials. GitHub reports no impact to customer data, enterprise accounts, or public repositories at this stage.

Which VS Code extension was used in the GitHub breach?

GitHub has not publicly named the specific extension involved in its internal breach. The same threat actor, TeamPCP, separately compromised the Nx Console VS Code extension (nrwl.angular-console v18.95.0) on May 18, 2026 by publishing a malicious version to the VS Code Marketplace using stolen publisher credentials.

What is the .vscode/tasks.json autorun mechanism?

Visual Studio Code can run tasks defined in .vscode/tasks.json when a folder is opened, using "runOptions": { "runOn": "folderOpen" }. In VS Code with Workspace Trust enabled, the IDE prompts before running. In Cursor, which ships with Workspace Trust off by default, the task executes silently the moment a developer opens the folder.

Who is TeamPCP and what is UNC6780?

TeamPCP is a financially motivated threat group tracked as UNC6780 by Google's threat intel group. The group is linked to a sequence of 2026 developer supply chain incidents, including the Mini Shai-Hulud npm and PyPI worm campaign in May, the Nx Console VS Code extension compromise, the durabletask PyPI package compromise, and an earlier SAP package compromise.

What is the Mini Shai-Hulud worm?

Mini Shai-Hulud is a self-propagating supply chain worm operated by TeamPCP that ran across the npm and PyPI ecosystems between May 10 and May 12, 2026, compromising over 170 packages including TanStack, Mistral AI, and OpenSearch dependencies. It harvests tokens from CI/CD pipelines and developer workstations, then uses those tokens to publish malicious versions of additional packages, often with valid SLSA Build Level 3 provenance from hijacked OIDC flows.

What is FIRESCALE?

FIRESCALE is a command-and-control fallback mechanism used in TeamPCP's Python toolkit. When the primary C2 server is unreachable, the malware searches GitHub's public commit messages worldwide for the pattern FIRESCALE <base64_url>.<base64_signature>, accepts only URLs whose RSA-SHA256 signature verifies against an embedded 4096-bit public key, and uses that URL as the new C2 address. A single public commit to any GitHub repository can re-point the entire worm.

How do you protect against malicious IDE extensions and .vscode/tasks.json autorun?

Enforce Workspace Trust in VS Code and Cursor across the fleet, set task.allowAutomaticTasks to off, scan repositories for .vscode/tasks.json files with runOn of folderOpen or folderOpened, inventory installed extensions against known-compromised versions, and rotate any token that touched a developer machine running an unverified extension in the past 30 days.

How does Cantina detect IDE-based supply chain attacks?

Cantina scans IDE configuration files, extension manifests, and dependency manifests inside every repo as findings rather than as plumbing, correlates developer endpoints with identities and token usage, and maps every secret to every place it was used so revocation is targeted instead of a guess.