A GitHub employee installed a VS Code extension. Standard practice for a developer — the kind of thing that happens hundreds of times a day across the industry. Except this version had been quietly poisoned, and within hours, ~3,800 of GitHub’s internal repositories were in the hands of a threat actor selling them on a cybercrime forum for $50,000.

This is the story of how it happened, how the worm behind it works, and what you need to do right now if you work in a development environment.

TL;DR

  • GitHub confirmed on May 20, 2026 that a poisoned VS Code extension compromised an employee’s device — the specific extension has not been publicly named
  • Threat actor TeamPCP claimed to have exfiltrated ~3,800 internal GitHub repositories; GitHub said the claim is “directionally consistent” with its investigation
  • The attack is linked to TeamPCP’s Mini Shai-Hulud worm — a self-propagating supply chain worm that persists inside .vscode/tasks.json and .claude/settings.json and survives package removal
  • GitHub said it has no evidence of customer data exposure; a fuller incident report is forthcoming
  • IOC to check now: IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner in commit history = active infection

What Actually Happened: Confirmed vs. Claimed

It’s worth being precise here, because the details matter and not everything reported has been verified.

What GitHub confirmed:

  • A GitHub employee’s device was compromised via a poisoned VS Code extension
  • GitHub detected the intrusion on May 20, 2026 and moved quickly: the endpoint was isolated, critical secrets were rotated, and the malicious extension was removed from the Marketplace
  • Exfiltration involved GitHub-internal repositories only
  • GitHub said it has “no evidence of impact to customer information outside internal repositories”
  • The attacker’s ~3,800-repository claim is “directionally consistent” with GitHub’s investigation so far
  • GitHub committed to publishing a fuller incident report upon completion

What TeamPCP claimed:

  • Exfiltration of approximately 4,000 private repositories containing proprietary source code
  • Data posted on the Breached cybercrime forum with a minimum ask of $50,000: “This is not a ransom — the best offer will get it”

What researchers assess but has not been confirmed:

  • The specific VS Code extension installed by the employee has not been publicly named
  • The breach is linked to TeamPCP’s ongoing Mini Shai-Hulud campaign, which is the most technically plausible explanation given the timing and TTPs — but a direct causal link has not been officially confirmed

The distinction matters. The breach is real and serious. The technical mechanism — Mini Shai-Hulud — is real and well-documented. The exact extension that served as the entry point is not yet public.


Who Is TeamPCP?

TeamPCP is a financially motivated threat group that has made developer infrastructure its primary hunting ground in 2026. Their attacks don’t target exposed servers or misconfigured APIs — they go directly after the tools developers use every day: package registries, CI/CD pipelines, IDE extensions.

Their signature move is turning the open-source trust model against the people who built it. When a developer sees a package from a familiar publisher, or an extension they’ve used before, they don’t audit it. They install it. TeamPCP has systematically exploited that assumption.

Their most technically sophisticated operation to date is the Mini Shai-Hulud worm — named after the giant sandworms of Dune, because once it gets in, it digs deep and doesn’t stop. Launched on May 11, 2026, the worm compromised over 170 npm and PyPI packages — including TanStack, Mistral AI, OpenSearch, Bitwarden CLI, LiteLLM, and Guardrails AI — across more than 400 malicious versions published within a five-hour window.

The GitHub breach nine days later is consistent with what happens when Mini Shai-Hulud has been running inside a developer environment: it harvests credentials, and those credentials unlock doors.


Technical Deep Dive: How Mini Shai-Hulud Works

Mini Shai-Hulud is not a simple backdoored package. It’s a multi-stage worm designed to spread itself, survive cleanup attempts, and exfiltrate credentials across an entire organization. Here’s the full kill chain.

Stage 1: Entry via Package Manager or Extension

The worm enters through a compromised package — either a malicious npm/PyPI package, a backdoored GitHub Action, or (as in the GitHub breach) a poisoned VS Code extension. In the Mini Shai-Hulud campaign, the VS Code extension confirmed as compromised was nrwl.angular-console v18.95.0, a legitimate Angular developer tool with a large install base.

For npm packages, the trigger is a preinstall or postinstall hook — code that runs automatically when you run npm install. The hook downloads the Bun JavaScript runtime from objects.githubusercontent.com and uses it to execute the payload. Bun is chosen deliberately: it’s not Node.js, so many security tools that monitor for suspicious Node execution miss it entirely.

Stage 2: Credential Harvesting (134 Paths)

Once executing, the worm sweeps over 134 filesystem paths and cloud APIs across Linux, macOS, and Windows looking for secrets:

  • GitHub PATs, OIDC tokens, and App tokens
  • npm and PyPI credentials
  • AWS access keys, Azure CLI cache, GCP service accounts
  • Kubernetes service account tokens
  • HashiCorp Vault KV entries
  • SSH private keys, .npmrc, .netrc, shell history
  • AI assistant configs (Claude Code, VS Code)
  • Cryptocurrency wallets (Bitcoin, Ethereum, Monero)

On CI/CD runners, the worm goes further. It executes encrypted Python scripts via sudo python3 -c to read process memory directly from Runner.Worker and Runner.Listener — extracting GitHub Actions secrets that are masked in logs and would never appear in environment variables.

Stage 3: Persistence That Survives Package Removal

This is what separates Mini Shai-Hulud from typical malware. Most developers, when they discover a compromised package, uninstall it and consider the matter closed. The worm anticipated this.

After credential harvest, it injects persistence hooks into configuration files that VS Code and Claude Code read automatically every time a project is opened:

  • .vscode/tasks.json — adds a folderOpen trigger that re-executes the payload each time the workspace opens
  • .claude/settings.json and .claude/setup.mjs — injects a SessionStart hook into Claude Code

These modifications are committed to every branch in the repository. A developer who cleans their machine but clones the repo again pulls the infection back in with it.

Beyond IDE configs, the worm also installs:

  • Python .pth import hooks in site-packages (litellm_init.pth) — executes on any Python startup
  • systemd user services (~/.config/sysmon/sysmon.py) — survives reboots
  • On Windows: PE payloads embedded in WAV files via steganography, dropped to the Startup folder

Stage 4: Self-Propagation

The credentials stolen in Stage 2 fund the worm’s expansion. Using harvested npm and PyPI publish tokens, the malware publishes new malicious versions of packages the victim has rights to — spreading to anyone who installs them downstream.

OIDC token abuse makes this particularly hard to detect. OIDC tokens are short-lived and designed to replace stored secrets in CI/CD. The worm steals them from ACTIONS_ID_TOKEN_REQUEST_URL during workflow execution and uses them to publish packages with cryptographically valid Sigstore provenance bundles — the packages appear legitimately signed.

Stage 5: Exfiltration and C2

Stolen data is exfiltrated through a four-level fallback architecture:

  1. Primary: HTTPS POST to git-tanstack.com:443/router, encrypted AES-256-GCM + RSA-OAEP
  2. C2 discovery: Signed commits in the thebeautifulmarchoftime GitHub repository, where the operator posts new C2 domains
  3. Dune-themed repositories: If the primary C2 is down, the worm creates sardaukar-named repos in the attacker’s GitHub account and commits encrypted data there
  4. Victim account fallback: As a last resort, uses the victim’s own harvested GitHub token to commit encrypted exfil data to their repositories — along with the following commit message, which functions as a dead man’s switch:

IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner

If you see that string anywhere in your commit history or logs, the machine is actively infected.


Red Team Perspective: Why This Attack Architecture Works

Developer machines are the most valuable initial access point in most organizations — and the least hardened. A compromised developer’s workstation often has:

  • Direct write access to production repositories and CI/CD pipelines
  • Cloud credentials for building and deploying services
  • SSH keys to internal infrastructure
  • OAuth tokens to third-party services

Mini Shai-Hulud exploits the specific trust models that make developer tooling functional:

  • Package manager hooks (preinstall/postinstall) exist for legitimate setup automation — you can’t just disable them without breaking real functionality
  • IDE extension permissions mirror the user’s OS permissions — VS Code has no sandboxing model for extensions
  • OIDC was supposed to fix secret storage — TeamPCP turned the solution into the attack surface by stealing tokens mid-workflow
  • Sigstore signing was supposed to guarantee package provenance — the worm steals the signing capability along with the credentials

The self-propagation model is what makes containment genuinely difficult. Once a repository is compromised, every developer who clones it and opens it in VS Code or Claude Code reinfects their machine — even after the original malicious package has been removed from the registry.


Blue Team Perspective: Detection and Response

Indicators of Compromise to Check Right Now

Terminal window
# Dead man's switch string in git history (highest fidelity IOC)
git log --all --oneline | grep -i "revokeThis"
# Python backdoor on disk
ls -la ~/.local/share/kitty/cat.py
ls -la ~/.config/sysmon/sysmon.py
# Check VS Code extension list for the confirmed compromised extension
code --list-extensions | grep -i "angular-console"
# Inspect workspace config files for injected hooks
grep -r "folderOpen\|SessionStart\|setup_bun\|router_init\|bun_installer" \
.vscode/ .claude/ 2>/dev/null
# Check for C2 domains in DNS cache or hosts
grep -i "git-tanstack\|checkmarx.zone\|checkmarx.cx" /etc/hosts

Key IOCs

IndicatorTypeNotes
git-tanstack.comC2 domainPrimary exfil endpoint
audit.checkmarx[.]cxC2 domainTyposquats legitimate Checkmarx
checkmarx[.]zoneC2 domainSecondary C2
IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwnerCommit messageActive infection marker
dependabot/github_actions/format/setup-formatterBranch nameFake Dependabot branch used for persistence
github-advanced-security[bot]Commit authorImpersonation — check commits from this identity on non-CodeQL workflows
router_init.js, setup_bun.js, environment_source.jsPayload filenamesFound in compromised packages
litellm_init.pthPersistence filePython import hook in site-packages

SIEM Detection Rules

Write alerts for:

  • bun binary execution spawned from npm, VS Code, or Python processes
  • HTTP requests to objects.githubusercontent.com initiated by npm install or postinstall hooks
  • New .pth files created in Python site-packages directories
  • dependabot/github_actions/format/* branches created by non-Dependabot actors
  • sudo python3 -c spawned from npm or Bun contexts
  • Files written to .vscode/tasks.json or .claude/settings.json by package manager processes

Extension Vetting Policy

The VS Code Marketplace does not perform security analysis on every extension version. Treat extensions as you would third-party code in production:

ControlHow to implement
Extension allowlistEnforce via VS Code extensions.allowedExtensionIDs machine policy or MDM — note that .vscode/extensions.json is recommendations only, not enforcement
Version pinningPin versions in managed devcontainer images; VS Code has no native extension version-lock
Publisher verificationCheck publisher history and GitHub org before installing; verify the publisher’s identity is consistent with their previous extensions
Update reviewTreat extension minor version bumps as untrusted code — review changelogs and source diffs before approving

CI/CD Hardening Against OIDC Abuse

# Scope OIDC permissions per job — never grant id-token: write globally
permissions:
id-token: write # Only in jobs that actually publish
contents: read
# Add trusted publishing verification where supported (PyPI, npm)
# Enable Artifact Attestation for packages published from CI
  • Audit which workflows have id-token: write — most don’t need it
  • Monitor CI accounts for unexpected package publications
  • Require two-factor approval for registry publishes from automated workflows

Incident Response If Exposed

If any machine in your environment may have run a compromised extension or package version:

  1. Assume full credential exposure — every secret accessible from that machine
  2. Rotate GitHub PATs, SSH keys, and app tokens immediately
  3. Revoke and reissue cloud credentials (AWS, GCP, Azure, Kubernetes)
  4. Audit every repository the machine had access to for injected .vscode/tasks.json or .claude/ modifications
  5. Search commit history across all affected repos for the dead man’s switch IOC
  6. Check GitHub’s security log for unexpected access patterns during the window
  7. Re-examine all packages published from CI during the exposure window for tarball-vs-source divergence

What You Can Do Today

Developers:

  • Run code --list-extensions and remove anything unfamiliar or unused — if you don’t know what it does, it shouldn’t be installed
  • Grep your project directories for folderOpen and SessionStart entries in .vscode/tasks.json and .claude/settings.json
  • Check ~/.local/share/kitty/cat.py and ~/.config/sysmon/sysmon.py — these should not exist on a clean machine
  • Never store long-lived credentials in repository files, even in private repos

Security teams:

  • Build and enforce an approved extension list via VS Code policy or MDM — the Marketplace is not a safe harbor
  • Add SIEM detection for Bun execution during npm install and for writes to IDE config files by package manager processes
  • Require SBOM for packages published from CI pipelines
  • Audit all id-token: write grants across your GitHub Actions workflows

DevSecOps:

  • Implement tarball-vs-source verification for all packages your pipelines consume
  • Pin extension versions in devcontainer.json — accept updates only after manual review
  • Add integrity checks for .vscode/ and .claude/ directories to your pre-commit hooks


Sources