From one phishing email
to the compromise of GitHub itself.
The eleven-month rise of TeamPCP (Google calls them UNC6780) - and what it tells us about the next era of supply chain attacks.
For the canonical fact base, see the IOC dataset and the chain graph on the main research page.
May 20, 2026
At 04:17 EDT - May 20, 2026 - GitHub published a statement on X confirming that an employee's device had been compromised through a poisoned VS Code extension. Around 3,800 of GitHub's own internal source-code repositories were exfiltrated. The threat group TeamPCP - formally tracked by Google's Threat Intelligence Group as UNC6780- claimed the breach on the cybercrime forum “Breached” and demanded a minimum of $50,000 in Monero.
GitHub says no customer data was affected. The extension name has not been disclosed yet.
This is not a one-off. It is the latest hop in an eleven-month campaign that has now compromised an estimated 1,000+ SaaS environments (with Mandiant projecting growth to 5,000-10,000), harvested ~500,000 credentials, exfiltrated 300+ GB of data, and crossed every package-management ecosystem developers use to ship software: npm, PyPI, Docker Hub, GitHub Actions, OpenVSX, the VS Code Marketplace, and Jenkins.
This is the story of how it got here, and what it tells us about how the next campaign will look.
The setup (2024 → 2025)
TeamPCP did not begin with supply chain attacks. According to threat intelligence from Okta and reporting from Wiz and Help Net Security at the time, the group spent 2024 on something quieter: hijacking misconfigured Docker APIs, Kubernetes clusters, Ray dashboards, and Redis servers across the public internet, and using them to mine cryptocurrency.
It was unglamorous work - but in retrospect it was probably where the toolkit was built. The credential-harvesting routine that would later define their supply chain payloads - a sweep of 50+ filesystem paths for cloud secrets, an IMDS scrape for AWS metadata, a Kubernetes service-account token snatch - was written and field-tested against opportunistic cryptomining victims years before it was pointed at Trivy.
July 2025: the first phishing campaign
In July 2025, TeamPCP ran an operation that was not publicly connected to the group until Okta's threat-intelligence team retroactively stitched it into the TeamPCP timeline in May 2026: they pulled the publicly-listed email addresses off PyPI package metadata and ran a targeted phishing campaign against four maintainers. All four fell. The popular Python utility num2words received a malicious update, and the attackers created two long-lived malicious API tokens on the compromised accounts as a persistence mechanism beyond the initial passwords.
This is the earliest documented direct package-registry compromise in TeamPCP's timeline, and almost nobody outside Okta's threat team noticed it at the time. It established the playbook that would scale.
September 2025: the 2FA-reset pretext
Two months later, in September 2025, the group ran a different variation on npm. They convinced a maintainer to disable two-factor authentication using a phishing pretext - most likely a social-engineering conversation that ended with the maintainer running through a support-flow 2FA reset. Eighteen widely-used packages were poisoned with crypto-wallet stealing malware. Okta describes the aggregate as “hundreds of thousands of installs per week.”
December 2025: React2Shell, the mass exploit
By December 2025, TeamPCP had built enough automation to do something at scale. The React2Shell campaign ( CVE-2025-55182) mass- exploited a Next.js vulnerability, hitting 59,000 systems in 33 hours. Unit 42 dates the group's public notoriety from this campaign and notes their use of port 666 for “nearly all exploitation operations” - a signature loud enough that it became recognizable to network defenders.
By the start of February 2026, then, TeamPCP was already a mature operator with three documented modes - opportunistic cryptomining, phishing-driven maintainer-account takeover, and CVE-driven mass exploitation - and a credential-stealing toolchain field-tested against thousands of victims. What came next changed the rules.
One missed rotation
On February 27, 2026, a GitHub account named “MegaGame10418” opened pull request #10252 against the Aqua Security trivy repository. The PR exploited a misconfigured GitHub Actions workflow called “API Diff Check,” which used the dangerous pull_request_target trigger. That trigger lets a workflow run with the base repository's secrets while executing code from a forked pull request - and the workflow, in this case, did exactly that. A curl-to-bash payload read /proc/<pid>/mem from the runner's process tree and extracted the aqua-bot Personal Access Token, which had repo scope across the entire aquasecurity GitHub organization. The PAT was exfiltrated to recv.hackmoltrepeat[.]com.
The hackerbot-claw account that opened the PR is, per Wiz and ARMO, an autonomous AI agent whose profile describes a “vulnerability pattern index covering 9 classes and 47 sub-patterns.” Both research teams explicitly note that hackerbot-claw and TeamPCP are distinct actors. TeamPCP exploited the access hackerbot-claw obtained. The acquisition mechanism - whether the access was sold, leaked, or otherwise transferred - has not been disclosed.
Aqua detected the breach and disclosed it publicly on March 1. They rotated credentials. And here is the single most consequential defender error in the entire cascade: the rotation was not atomic. Either the old token retained access during the window between issuance of the new token and revocation of the old, or the attacker observed the new token in transit. Either way, the access persisted for twenty more days.
The strike, and what makes a tag “immutable”
At 17:43 UTC on March 19, TeamPCP used the still-valid aqua-bot credential to force-push 76 of Trivy's 77 release tags to malicious imposter commits. (One community source - the ugurrates technical timeline - counts 75 of 76; SANS's Hartman and Johnson and OSM's Paul McCarty independently count 76 of 77. Most likely an inventory delta in how each source counted; included here so the reader knows what contradiction exists in the public record.)
Each tag pointed at an imposter commit that cloned the original's metadata - author, timestamp, message - while substituting entrypoint.sh with a 204-line credential stealer that self-identified as “TeamPCP Cloud stealer.” (Google's threat-intelligence team has since named this malware family SANDCLOCK.) The stealer reads /proc/<pid>/mem from Runner.Worker processes searching for {"value":"<secret>","isSecret":true} patterns, then sweeps fifty-plus filesystem paths for credentials. It encrypts the harvest with an AES-256-CBC session key wrapped in attacker-controlled RSA-4096, and exfiltrates to scan.aquasecurtiy[.]org - a typosquat of aquasecurity.org that passes a casual log review.
The tags still passed GitHub's “Immutable” badge check, because that check looks at the tag string, not the commit it points at. Force-pushing a tag to a different commit is not a violation of any guarantee GitHub Actions makes. The mechanism was working as designed. That is the story, and we will return to it in the insights section.
The eight-day cascade
From March 19 through March 27, TeamPCP systematically chained the credential harvest into five package ecosystems. Each compromise stole the credentials that fueled the next.
Day 1 - Trivy itself
474 public repositories ran the malicious trivy-action during the ~12-hour exposure window (Mar 19 17:43 → Mar 20 05:40 UTC). Every CI runner in that window leaked GitHub PATs, npm publishing tokens, PyPI tokens, AWS keys, Kubernetes service-account tokens, and Docker registry credentials.
Day 2 - CanisterWorm hits npm
At 20:45 UTC on March 20, stolen npm tokens fed a self-propagating worm Aikido later named CanisterWorm. It is the first npm malware family to use an Internet Computer Protocol (ICP) blockchain canister as command-and-control - a decentralized smart contract at tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0[.]io with no single takedown point. The worm went through four waves over the following weeks (Aikido documents the index.js and deploy.js SHA-256 hashes for each variant). The persistence service masqueraded as PostgreSQL monitoring (pgmon.service) and polled the canister every fifty minutes for new instructions.
By the time the wave saturated, sixty-six-plus npm packages across multiple maintainer namespaces (@emilgroup, @opengov, @v7, @teale.io) had been republished with the worm payload. The total artifact count - distinct package@version pairs - was 141.
Day 4 - Aqua's corporate org defaced + Iran wiper detonates
On March 22, in a scripted two-minute burst starting at 20:31 UTC, TeamPCP renamed every one of the 44 repositories in Aqua's aquasec-com corporate GitHub organization. The token used was Argon-DevOps-Mgt, harvested from a Trivy CI runner during the Mar 19 window.
The same day, Aikido's Charlie Eriksen identified the most-disturbing component of the wave: a destructive variant gated to Iranian systems. The payload checks /etc/timezone or timedatectl for Asia/Tehran or Iran and the fa_IR locale. On Iranian Kubernetes clusters it deploys a privileged DaemonSet named host-provisioner-iran with a container called kamikaze, mounts the host root, runs find /mnt/host -maxdepth 1 -not -name 'mnt' -exec rm -rf + and then reboot -f. On standalone Iranian hosts: rm -rf / --no-preserve-root. Aikido's framing: TeamPCP is “prepared to be destructive when they want to be.”
Day 5 - Checkmarx (twice)
On March 23 at 12:53 UTC, TeamPCP republished two Checkmarx-published OpenVSX extensions (ast-results v2.53.0 and cx-dev-assist v1.7.0, twelve seconds apart) via the compromised ast-phoenix publisher account. Five minutes later, at 12:58 UTC, they force-pushed all 35 tags of Checkmarx/kics-github-action via the compromised cx-plugins-releases account. The Checkmarx C2 was a different typosquat: checkmarx[.]zone. Sysdig flagged a third Checkmarx GitHub Action that evening (ast-github-action v2.3.28).
Day 6 - LiteLLM, and the specific reason chains compound
On March 24, malicious litellmv1.82.7 and v1.82.8 went live on PyPI. Per Snyk's Stephen Thoemmes, the specific mechanism that made it possible was this: BerriAI's CI/CD pipeline fetched unpinned Trivy from apt. Every LiteLLM build automatically pulled the malicious version on its next run. The compromised action exfiltrated the PYPI_PUBLISH token to checkmarx.zone, and the attacker used it to publish.
This is the canonical case for action pinning - and it is going to come up again in the insights.
The malicious LiteLLM was live for approximately three hours (10:39 → 13:38 UTC) before PyPI quarantined it. In that window, 5,000-10,000 cloud environments downloaded a malicious version. Per Wiz Research cited by SANS, LiteLLM is present in 36% of the cloud environments Wiz monitors; it averages 95 million monthly PyPI downloads and 3.4 million daily. The damage potential of those three hours was enormous.
Day 9 - Telnyx and the .wav files
On March 27, using credentials harvested from LiteLLM's CI pipeline, TeamPCP published malicious versions of the Telnyx Python SDK (4.87.1 and 4.87.2). The payload introduced something new: it downloaded innocent- looking .wav files (hangup.wav on Windows, ringtone.wav on Linux/macOS) from C2, read the audio frames via Python's wave module, base64-decoded them, split off the first 8 bytes as an XOR key, and extracted the encrypted second-stage payload from the remainder. The XOR was not for cryptographic security - it was, per Socket, “designed to prevent the embedded payload from appearing as recognizable Python source code.” v4.87.1 had a Windows bug; v4.87.2 was a rapid bugfix, which Socket notes suggests “sustained access to the publishing credentials” rather than a one-shot opportunistic publish.
The pivot
After Telnyx, the pace of new compromises slowed. For three days, TeamPCP published no new package-registry attacks. Then on March 30, Help Net Security reported a strategic shift: TeamPCP announced a partnership on BreachForums with a new ransomware-as-a-service operation called Vect. The framing in the announcement was telling - Vect would “deploy ransomware across all affected companies” using TeamPCP's credential harvests. The labor was distributed; TeamPCP held the inventory.
By the time SANS published their blog on March 25, OSM had already documented evidence of a second partnership: a message in the LAPSUS$ Telegram channel reading “TeamPCP gonna do another large Supply chain attack, be ready for it”, followed by a reference to a “35k stars github repo” - a target telegraphed in a closed channel before any public indicator appeared. SANS reported a third collaboration with LAPSUS$ in their April coverage. Mandiant CTO Charles Carmakal confirmed the LAPSUS$ collaboration publicly at RSA Conference the same week.
Then, in early April, the harvest started selling. SANS's ISC update 007 documented something most defenders did not realize was happening: TeamPCP-harvested credentials were being used by ShinyHunters- a separate data-extortion crew - to access Cisco's development environment. Three hundred-plus private Cisco repositories were cloned. The haul included AI products, unreleased code, customer repositories from banks, business-process-outsourcing firms, and US government agencies. ShinyHunters set an April 3 deadline; Cisco has not publicly acknowledged payment or negotiations.
The supply-chain-attack-as-a-service stack was, by mid-April, fully assembled. TeamPCP harvested. Vect deployed ransomware. LAPSUS$ negotiated extortions. ShinyHunters operationalized credentials against high-value individual targets. By the end of April, Mandiant had documented 1,000+ compromised SaaS environments and was projecting growth to between 5,000 and 10,000.
The return and the leak
In late April, TeamPCP returned to direct compromises - and introduced a new worm. The first wave landed on April 23, when @bitwarden/cli@2026.4.0 went malicious. Socket caught the repository description had been changed to “Shai-Hulud: The Third Coming.” TeamPCP was self-naming.
Six days later, on April 29, the same worm family hit four SAP CAP packages - mbt, @cap-js/sqlite, @cap-js/postgres, and @cap-js/db-service- over a 15:25-17:43 UTC window. The repository description on the attacker-created dead-drop repos read “A Mini Shai-Hulud has Appeared.” And inside the payload, Snyk's Stephen Thoemmes found a log string that materially changes the geopolitical framing:
“Exiting as russian language detected!”
The Mini Shai-Hulud payload checks system locale on every install. If the host is Russian-language, execution is skipped. Combined with the Iran-only wiper from March, this points away from ideology toward something cooler: regulatory calculus. TeamPCP appears to be operating from a jurisdiction where avoiding Russian-language systems matters - most likely because of where they are or fear prosecution from. This is not idealism. This is a professional crew managing their legal risk.
The borrowed primitive
OSM's Paul McCarty was the first to recognize that the “novel” piece of Mini Shai-Hulud was not actually novel. The technique that made it execute silently - a.vscode/tasks.json with "runOn": "folderOpen" auto-running the moment a developer opens the workspace folder - was a direct lift from the DPRK-aligned PolinRider / TasksJacker campaign that ThreatLocker had documented running on 1,900+ public GitHub repositories in February.
Cross-pollination between APT-aligned and crimeware ecosystems is not new. What is new is the speed: a DPRK technique was publicly documented in February and was operationalized inside a financially-motivated worm by April. The technique window - between a defender publishing a detection rule and the next group reusing the primitive in a different family - has compressed to weeks.
The leak
Then, on May 12, 2026, TeamPCP did something no major financially-motivated supply chain actor had done before. OX Security's Moshe Siman Tov Bustan found it: TeamPCP themselves released their malware source code on GitHub, via what appear to be compromised GitHub accounts (agwagwagwa, headdirt, and tmechen - the third with a cat-themed profile picture, matching the PCPcat alias). The repos are searchable by the phrase “A Gift From TeamPCP.”
OX's framing of what this means is correct: “By going open source, they've handed any willing actor the tools to build their own variant.” Within five days, OX documented an unnamed copycat actor deploying verbatim, un-obfuscated copies of the leaked code against typosquatted chalk and axios packages.
Three more waves
The leak did not slow TeamPCP. On May 11, before the source release, they had launched Mini Shai-Hulud at scale through the TanStack ecosystem. The initial vector was novel: a malicious PR exploited pull_request_target against TanStack/router, poisoned the pnpm-store cache, and used the runtime memory extraction trick to harvest an OIDC token from inside Runner.Worker. With that token they minted fresh npm publish tokens via OIDC federation - and republished packages with valid Sigstore provenance attestations (issued by fulcio.sigstore.dev and recorded in rekor.sigstore.dev). To anyone downstream checking provenance as a trust signal, the malicious releases looked authentic.
By May 13, OSM was tracking the campaign across 170 npm packages in 19 namespaces, plus two PyPI packages. The highest-blast-radius hit was @opensearch-project/opensearch: the official OpenSearch client maintained by AWS, with 1.3 million weekly downloads. The Mistral AI clients were hit on both npm and PyPI simultaneously. On a defacement page hosted at the typosquat git-tanstack.com, TeamPCP signed their own work: “With Love TeamPCP / We've been online over 2 hours now stealing creds. Regardless I just came to say hello :^)”
Then on May 18-19, OSM's Paul McCarty caught what was - until then - the largest single republish event in the campaign. Two compromised npm maintainer accounts (atool and prop) republished 324 packages - including the entire AntV (Alibaba) data-visualization suite - in two tightly clustered waves on May 19 (01:39-02:06 UTC), with 314 versions in the second wave landing in approximately six seconds. The payload included a new beacon string - niagA oG eW ereH :duluH-iahS- reversed, “Shai-Hulud: Here We Go Again.”
Microsoft's durabletask PyPI package followed on May 18 at 15:08 UTC - a thirteen-minute publication window for three malicious versions, all using credentials that Wiz traced back to the earlier @antv compromise.
The GitHub hop
Which brings us back to May 20, 2026.
GitHub's statement, in full:
“Yesterday we detected and contained a compromise of an employee device involving a poisoned VS Code extension. We removed the malicious extension version, isolated the endpoint, and began incident response immediately.”
GitHub confirmed approximately 3,800 of their own internal repositories were exfiltrated. TeamPCP claims 4,000. The difference may be how each side counts repositories versus forks. GitHub says no customer information stored outside of their internal repos was affected.
TeamPCP listed the data for sale on the Breached cybercrime forum for a minimum of $50,000 in Monero, with the framing: “No low ball offers will be accepted, everything for the main platform is there and I very am happy to send samples to interested buyers to verify the absolute authenticity.” And, separately: “As always, this is not a ransom… 1 buyer and we shred the data on our end, it looks like our retirement is soon.”
On X, the TeamPCP-linked account @xploitrsturtle2 posted: “GitHub knew for hours, they delayed telling you and they won't be honest in the future. What an amazing run, it's been an honor to play around with the cats over the past few months.” A second account, @xpl0itrs, has separately signaled an intent to donate a portion of the proceeds to charity.
The extension name has not yet been disclosed by GitHub. Our chain-graph carries three hypothesized bridges from prior TeamPCP compromises - the Mini Shai-Hulud .vscode/ persistence is the strongest circumstantial match. If you have insight into which extension was involved, our research log is open.
Six insights for the industry
The 32 incidents in this story are the surface. Below is what we think they are telling us about how defenders need to operate from here forward.
1. Security tools are the highest-leverage attack surface in your stack.
Ahmad Nassri - Socket's CTO and the former CTO of npm - put it directly in Socket's coverage: “These tools are secret + infrastructure + code security scanners by design.” Trivy, Checkmarx KICS, LiteLLM, the durabletask client - every major incident in this campaign began by compromising a tool that defenders deploy to improve their security. TeamPCP's most quotable Telegram message captures the inversion: “These companies were built to protect your supply chains yet they can't even protect their own.” If your CI/CD pipeline does anything beyond build-and-test - if it has cloud credentials, package-publishing tokens, SSH keys, K8s service-account tokens - treat every security scanner in that pipeline as a possible exfiltration vector until proven otherwise.
2. Non-atomic credential rotation is a class of vulnerability we don't have a name for.
Aqua rotated aqua-bot on March 1. They thought they had revoked the attacker's access. They had not. Either the new token was observable to the attacker during the swap, or the old token retained API access during the overlap. We don't know which, because there is no vendor-neutral term for this failure mode and most incident reports do not look for it. There is no CVE class. There is no Mitre ATT&CK technique for it. We should write one. Until that exists, the practical rule is: revoke before you issue, and verify revocation against the API's list of active tokens before you call the rotation complete.
3. Sigstore provenance is necessary but no longer sufficient.
When TeamPCP stole OIDC tokens from the TanStack CI runner, they did not just publish malicious npm releases - they attached valid Sigstore provenance attestations by calling fulcio.sigstore.dev and rekor.sigstore.dev the same way a legitimate build would. To anyone downstream verifying the attestation alone, the releases looked clean. The lesson is not that Sigstore failed. Sigstore worked exactly as designed: it attested that the code was built where it was claimed to be built. The defender needs the layer above that: an attestation about which workflow at which moment was authorized to publish - and a registry that enforces it. PyPI Trusted Publishers gets you there for Python. npm provenance with workflow-scoped (not repo-scoped) OIDC trust configuration is the goal everywhere else.
4. AI-IDE workflows are a new code-execution channel we haven't hardened.
Mini Shai-Hulud injects a .claude/settings.json file containing a SessionStart hook alongside the older .vscode/tasks.json trick. When Claude Code attaches to the workspace, the hook fires the payload. The primitive is the same - execute code in response to a tooling event - but the attack surface is new. Most enterprise EDR configurations do not flag arbitrary code execution by an AI agent the same way they flag it from a developer's shell. They should. Until they do: review what your AI assistants are configured to auto-execute, and treat .claude/ + .vscode/ as code- execution surfaces equivalent to ~/.bash_profile.
5. Crimeware tradecraft is closing the gap with APT - fast.
The tasks.json folderOpen primitive was first documented at scale by ThreatLocker against DPRK-aligned PolinRider in February 2026. By April 29, it was inside TeamPCP's Mini Shai-Hulud - a financially-motivated worm. Eight weeks. That is the window we are now working in. The rule of thumb that “APT TTPs take six to twelve months to land in commodity malware” needs to be retired. Defenders should write detections against the technique classes - folder-open exec, OIDC token theft from runner memory, Sigstore attestation minting from stolen tokens - rather than against specific campaigns.
6. Package-manager defaults matter more than user training.
Two examples illustrate this. First: the entire Mini Shai-Hulud worm fires from preinstall hooks. npm install --ignore-scripts as a default would have neutered every variant in this campaign. pnpm v10 ships this kind of secure-by-default lifecycle policy out of the box; npm does not. Second: pnpm has a minimum-release-age setting - a 24-hour default available out of the box - that refuses to install package versions less than N hours old. The @antv mass-republish was live for a 22-minute automated burst on May 19. A 24-hour cooldown would have caught it before it reached production deploys. The tooling default is the control. User training is not.
The summary, in one sentence
Every effective control in this story is one that runs by default on every developer's machine: pinned actions, ignored scripts, minimum-release-age, passkey-only logins, workflow-scoped publish trust. The future of supply chain defense is the boring work of making those defaults the only defaults. Everything else is rear-guard.
What we don't know yet
A few open threads worth flagging as of publication: the VS Code extension name in the May 20 GitHub breach (undisclosed), the Cisco-side response to the ShinyHunters extortion (no public statement five weeks on), and the exact credential pathway from TeamPCP's harvest into ShinyHunters' Cisco operation. MITRE ATT&CK has not yet catalogued TeamPCP, UNC6780, or SANDCLOCK - normal lag for an emerging actor, but worth knowing: every claim in this article traces to vendor publications and community researchers, not to MITRE-canonical entries that don't yet exist.
Sources and method
This article sits on top of a structured research package that, at publication, contains:
- 32 incidents, each with plain-language and technical descriptions, ISO dates, and source citations
- 39 directed chain edges, each with evidence and a confidence rating (confirmed / strong / hypothesized)
- 18 domains/IPs, 22 SHA-256 hashes, 19 actor handles, 4 malware families, 12 verbatim operator quotes, 30 credited researchers in the IOC dataset
- Reconciled contradictions surfaced inline, with each conflicting source named and the resolution shown
The interactive chain graph, the defender checklist, and the full IOC dataset live on the main research page. Every source URL referenced in this article is listed in the Sources block there.
Primary research credits go to the threat-intelligence teams whose work made this article possible: Rami McCarthy (Wiz / ramimac.me), Paul “6mile” McCarty + Jenn (OpenSourceMalware), Liran Tal + Stephen Thoemmes (Snyk), Charlie Eriksen (Aikido), Moshe Siman Tov Bustan + Nir Zadok (OX Security), Jeremy Kirk + George Wang (Okta Threat Intelligence), Kenneth Hartman + Eric Johnson (SANS), Ugur Can Ates (community technical timeline), Sarah Gooding + Ahmad Nassri (Socket), Adnan Khan, Callum McMahon (FutureSearch), Lloyd Davies, and the Unit 42, Wiz Research, Mandiant, ThreatLocker, JFrog, Sysdig, StepSecurity, Aikido, Endor Labs, SafeDep, and CSA AI Safety Initiative teams. This story exists because they published.
Corrections welcome and credited. Email support@pluto.security or reach Yotam Perkal ↗.