Snyk Blog In this article TL;DR Affected packages How the attack works Stage 1: Maintainer account compromise Stage 2: Automated malicious publish Stage 3: Orphan commit injection for Sigstore provenance Stage 4: Credential harvesting Stage 5: Data exfiltration Stage 6: Persistence Stage 7: Worm propagation Impact analysis Detection Remediation Step 1: Remove persistence before revoking tokens. Step 2: Clean npm and reinstall on clean versions. Step 3: Rotate all credentials. Step 4: Audit GitHub for injected workflows and dead-drop repos. Step 5: Prevent future exposure. The bigger campaign: Shai-Hulud Waves Attack timeline (May 19, 2026, UTC) Snyk coverage Mini Shai-Hulud Hits AntV: 300+ Malicious npm Packages Published via Compromised Maintainer Account Written by Liran Tal May 18, 2026 0 mins read A supply chain attack affecting the @antv data visualization ecosystem and related npm packages is actively spreading through the npm registry. The attack, attributed to a threat group called TeamPCP and branded as another wave of the Mini Shai-Hulud campaign, published more than 300 malicious package versions across 323 packages in a 22-minute automated burst on May 19, 2026. The packages collectively represent approximately 16 million weekly downloads. The attack vector was a compromised npm maintainer account. The malware embedded in affected packages harvests developer secrets and cloud credentials, establishes persistent C2 access, and attempts to self-propagate to additional packages using stolen npm tokens. TL;DR Attack type Supply chain, compromised maintainer account Threat actor TeamPCP (aliases: DeadCatx3, PCPcat) Campaign Mini Shai-Hulud (ongoing since Sep 2025) Incident date May 19, 2026, 01:39–02:06 UTC Packages compromised 637 malicious versions across 323 packages Estimated weekly downloads ~16 million Malware behavior Credential theft, cloud secret harvesting, persistence, worm propagation Snyk coverage Advisories in Snyk Vulnerability Database ; Zero Day Report available in-app Immediate action Pin to pre-May 19 versions, run npm install --ignore-scripts , rotate all credentials Affected packages The compromised atool npm account maintained 547 packages. The malicious publish window hit over 300 of them in two waves: First wave : 01:39–01:56 UTC (~317 versions) Second wave : 02:05–02:06 UTC (~314 versions) The highest-download packages affected include: Package Snyk Advisor Monthly Downloads size-sensor Snyk Package Page Health for size sensor 4.2M echarts-for-react Snyk Package Page Health for echarts-for-react 3.8M @antv/scale Snyk Package Page Health for @antv/scael 2.2M timeago.js Snyk Package Page Health for timeago.js 1.15M @antv/g6 Snyk Package Page Health for @antv/g6 1.0M+ Core @antv packages compromised include @antv/g2 , @antv/g6 , @antv/x6 , @antv/l7 , @antv/s2 , @antv/f2 , @antv/g , @antv/g2plot , @antv/graphin , and @antv/data-set , along with non-scoped packages like echarts-for-react , timeago.js , size-sensor , and canvas-nest.js . AntV is an Alibaba-originated data visualization suite widely used across enterprise dashboards, financial reporting tools, and graph analysis platforms. Its breadth of adoption makes the maintainer account's package portfolio an unusually high-value target. How the attack works Stage 1: Maintainer account compromise The attack begins with the atool npm account being compromised. How the account was obtained is still under investigation. With control of the account, the attacker had publish access to all 547 packages it maintains. Stage 2: Automated malicious publish The attacker published malicious versions in two rapid waves, pushing most packages twice (a small number of early-testing packages received three versions). Each malicious package tarball contains two additions: A root-level index.js: a 498KB heavily obfuscated Bun JavaScript payload A modification to package.json adding: "preinstall": "bun run index.js" The preinstall lifecycle hook triggers automatically when a developer runs npm install, before any other installation logic executes. Stage 3: Orphan commit injection for Sigstore provenance One of the more subtle techniques in this wave is the injection of an optional dependency pointing to an orphan commit in the legitimate antvis/G2 repository: "optionalDependencies" : { "@antv/setup" : "github:antvis/G2#1916faa365f2788b6e193514872d51a242876569" } Important to note that the GitHub-sourced dependency is the same method of attack that was used in the previous TanStack Shai-Hulud supply chain attack . The commit was authored by the attacker but forged to appear as huiyu.zjt <huiyu.zjt@ant.com> (a real maintainer). No write access to the target repository was needed. The attacker forked antvis/G2 , created an orphan commit carrying the payload, then deleted the fork. GitHub's object storage retains commits from deleted forks until garbage collection runs, leaving the malicious commit accessible via its hash. Fetching that commit provides a path to execute the payload with Git-sourced credentials while appearing to reference a trusted repository. Using stolen GitHub Actions OIDC tokens, the malware can then request signing certificates from Fulcio ( https://fulcio.sigstore.dev ) and create in-toto provenance statements via Rekor ( https://rekor.sigstore.dev ), producing packages with cryptographically valid SLSA Build Level 3 attestations. The key insight here is that the signatures are legitimate because the build pipeline itself was compromised. Sigstore provenance tells you which pipeline produced an artifact, not whether that pipeline was behaving as intended. And so, a common misconception about relying solely on attestation evidence for published packages is that it is a bulletproof signal of legitimacy. Stage 4: Credential harvesting When bun run index.js executes on a developer's machine or in a CI runner, the payload targets over 80 environment variables and 100+ file paths. Targeted credential types include: AWS : Access keys ( AKIA[0-9A-Z]{16} ), session tokens, EC2 IMDS ( 169.254.169.254 ), ECS metadata ( 169.254.170.2 ), Secrets Manager GCP : Service account JSON, application default credentials Azure : Service principal credentials GitHub : PATs and OIDC tokens ( gh[op]_[A-Za-z0-9]{36,} ) npm : Publish tokens with bypass_2fa scope Infrastructure : Kubernetes service tokens, HashiCorp Vault tokens Databases : MongoDB, MySQL, PostgreSQL, Redis connection strings Services : Stripe keys, Slack tokens, Docker auth configs SSH keys : ~/.ssh/id_* In GitHub Actions environments, the payload attempts to read secrets directly from the Runner.Worker process memory via /proc/{pid}/mem , bypassing secret masking entirely. Stage 5: Data exfiltration All harvested credentials are serialized as JSON, gzip-compressed, encrypted with AES-256-GCM, and then the encryption key is wrapped using RSA-OAEP against a hardcoded attacker public key. This means defenders who find the exfiltrated data cannot determine what was stolen. Exfiltration routes through two channels: Primary C2 : https://t[.]m-kosche[.]com:443/api/public/otel/v1/traces (disguised as OpenTelemetry trace data) Secondary GitHub dead-drop : If a GitHub token is found, the payload creates repositories on the victim account with Dune-themed names (combinations of words like sardaukar , fremen , atreides combined with sandworm , ornithopter , stillsuit , plus a number 0-999), containing README files with characteristic phrases from previous waves of the Shai-Hulud campaign, reversed (e.g. “niagA oG eW ereH :duluH-iahS”). Stolen data is committed as results/results-<timestamp>-<counter>.json . HTTP requests use "python-requests/2.31.0" as the User-Agent. Stage 6: Persistence The payload installs several persistence mechanisms that survive a simple npm uninstall : AI coding agent hijacking : Creates .claude/settings.json with a SessionStart hook executing node .claude/setup.mjs . This re-executes the malware whenever a developer opens a new Claude Code session in an affected directory. We’ll note that this is also a similar pattern we’ve seen in prior supply chain attack campaigns such as TanStack. IDE hooks : Modifies .vscode/tasks.json with "runOn": "folderOpen" to trigger on project open. OS-level daemon : Installs ~/.local/share/kitty/cat.py , a background daemon that polls the GitHub commit search API hourly for commands containing the keyword firedalazer . Commands are RSA-PSS signed with a hardcoded 4096-bit public key, allowing the attacker to push arbitrary Python code to run on compromised machines long after the initial infection. The daemon is registered as a systemd user service on Linux ( ~/.config/systemd/user/kitty-monitor.service ) and a LaunchAgent on macOS ( ~/Library/LaunchAgents/com.user.kitty-monitor.plist ). Token monitor : ~/.local/bin/gh-token-monitor.sh polls stolen GitHub tokens at 60-second intervals, enabling the attacker to react quickly if a token is about to expire. Stage 7: Worm propagation The payload searches for npm tokens with the bypass_2fa scope and uses them to republish additional packages that the compromised account can publish to. For GitHub Actions environments, it exchanges the Actions OIDC token for per-package npm publish tokens via: POST https: //registry.npmjs.org/-/npm/v1/oidc/token/exchange/package/<package-name> It also injects a GitHub Actions workflow on a branch named chore/add-codeql-static-analysis , with the workflow file named "Run Copilot" ( .github/workflows/codeql.yml ). The workflow dumps toJSON(secrets) to an artifact named format-results.txt , then self-cleans by deleting the workflow run. Impact analysis The direct blast radius is any developer or CI environment that ran npm install against an affected package version between 01:39 and approximately 02:18 UTC on May 19, 2026. CI/CD environments are at elevated risk. In those contexts, the payload can read all secrets from the runner process, not just those explicitly pass