The TeamPCP Campaign Retrospective: One Operator, Five Compromises, Six Weeks

Written by the Rafter Team

The TeamPCP attribution has shown up in five separate Rafter incident posts in the past six weeks. Reading them in publication order looks like a string of unconnected supply-chain compromises. Reading them in campaign order — operator timeline, target selection, payload evolution — looks like one mid-sized criminal operation executing a deliberate playbook.
This post is the synthesis: what TeamPCP did, in what order, with what tooling, and which defensive failures repeated across every target.
The TeamPCP playbook is reproducible. Any criminal operator with a moderate budget can replicate the workflow within a quarter. The defensive lessons are not specific to this campaign — they are specific to the class of "compromised maintainer credential → published malicious version → install-time payload" that TeamPCP exemplified.
The campaign timeline
The five disclosed compromises Rafter has covered, in order of public disclosure:
| Date | Target | Vector | Payload class |
|---|---|---|---|
| Mid-Mar 2026 | Trivy / TeamPCP first surface | Maintainer credential | Wallet stealer + RAT |
| Late Mar 2026 | Checkmarx-adjacent package | Maintainer credential | Wallet stealer |
| Early Apr 2026 | SAP Mini-Shai-Hulud variant | Worm + credential | Worm spread |
| Mid-Apr 2026 | PyTorch Lightning impersonation | Typosquat + brand spoof | RAT + wallet stealer |
| Late Apr 2026 | Intercom-client downstream | Transitive dependency | Wallet stealer |
The five events look distinct on disclosure-date axes. Re-ordered by tooling reuse — same exfiltration endpoint pattern, same payload binary structure, same maintainer-account-takeover technique — they collapse into one operator's six-week ramp.
What the operator did well
Three operational choices made TeamPCP effective despite using none of the techniques that get nation-state actors press.
The targets were second-tier visible. None of the compromised packages were household names. They were all packages that a senior developer would recognize on sight and trust without checking, but that a security team would not have specifically flagged in a threat-modeling exercise. That gap — too visible to be obscure, too obscure to be policed — is where the campaign lived.
The payload was off-the-shelf. TeamPCP used a recognizable wallet-stealer family, lightly customized per target. The customization was the file paths and the exfiltration domain. The actual stealer logic appears to have been licensed or copied from a commodity malware kit. This is operationally cheap and makes the defender's "write a YARA rule for this campaign" job harder, because the underlying binary is shared across multiple unrelated campaigns.
The credential takeover was lateral. TeamPCP did not crack one maintainer's password. They appear to have compromised a developer machine in a small organization, harvested every credential resident on that machine, and used the resulting collection across multiple package registries over weeks. The campaign is one host compromise expressed across five different registry surfaces.
What the operator did poorly
The campaign is not flawless. Two operational mistakes have been catalogued.
The first is exfiltration-domain reuse. The same wildcard CNAME pattern appears across three of the five compromises, with only the second-level label rotating. Once one campaign domain was burned, the rotation pattern was discoverable by automated correlation. This is the artifact that lets us attribute the campaign as one actor in the first place.
The second is timezone leak in commit metadata. Three of the credential-takeover commits — pushed under the legitimate maintainer's identity but inserting the payload — were authored in a timezone consistent across all three but inconsistent with the actual maintainer's stated location. That is a soft signal, not proof, but combined with the domain reuse it tips the attribution from "possible" to "consistent."
These are not the mistakes that get an operator caught quickly. They are the mistakes that let defenders treat the campaign as one actor instead of five — which is the difference between five disparate incident postmortems and one campaign retrospective with actionable repeated lessons.
The defensive lessons that repeated across every target
Five compromises. The same gap let each one in.
Maintainer credential security is not maintainer-grade. Every TeamPCP compromise that involved an account takeover used a password or a session token that was harvested from a developer machine. None used a 2FA bypass. None used a registry-side exploit. The defensive control that would have blocked the entire campaign is "every package-publishing credential lives behind hardware-attested 2FA, scoped per-publish." Most registries support this. Most maintainers do not enable it. The gap is not technical — it is operational.
Install-time payloads ran without containment. Every payload in the campaign executed at install time. A user installing the compromised package in a development environment without install-time sandboxing was instantly compromised. The defensive control is the same one called out in postinstall hooks as a bug class — disable install scripts by default, sandbox installs without network egress, hash-pin dependencies. The control existed before TeamPCP and would still have blocked every campaign installer.
Transitive surprise was the actual blast radius. Most of the victims of the TeamPCP campaign did not install a TeamPCP-compromised package directly. They installed a package that depended on a package that depended on a TeamPCP-compromised package. The blast radius was not "who installs the compromised library." It was "who has any dependency tree that ever resolves to the compromised library." That radius is, in practice, almost everyone.
What changes if you treat this as one campaign
The compounding insight is that TeamPCP is not an unusual operator — it is an average operator running an average playbook with adequate operational security. The five disclosed compromises are the visible cases. The same operator likely has more compromised credentials, more in-flight packages, and more rotation of exfiltration infrastructure than the five publicly-disclosed events suggest.
A defender posture that treats supply-chain attacks as one-off incidents will calibrate the threat as rare. A posture that treats them as the steady output of a small number of mid-sized operators will calibrate the threat as continuous. The latter is correct.
The Rafter angle
The TeamPCP class of attack is exactly the class Rafter's SCA pass surfaces. rafter run checks your dependency tree for known-compromised versions, postinstall hooks on freshly-published packages, and unpinned dependency declarations that admit silent version drift. None of those controls would have prevented TeamPCP from compromising the upstream maintainer. All of them would have prevented your code from installing the compromised version after the fact.
The defender's job in this class is not to stop the operator from compromising the upstream. The defender's job is to make sure that when the operator succeeds upstream — which happens on a roughly six-week cadence at the current scale — the compromised version does not get installed in your build without your security team noticing.