Date Observed: May 19, 2026
Ecosystem: npm (Node.js)
Targets: @antV data visualization packages, echarts-for-react, timeago.js, size-sensor, canvas-nest.js, CI/CD pipelines, developer workstations.
Attack Type: Maintainer account compromise, preinstall hook injection, optionalDependencies abuse, npm worm propagation
Impact: 639 malicious package versions across 323 npm packages; 2,700+ GitHub exfiltration repositories created; cloud credentials, tokens, and SSH keys stolen
Key Takeaways
- One compromised npm account (atool) gave attackers publish access to 547 packages; 639 malicious versions live under 30 minutes.
- The payload fires during npm install via a preinstall hook, running before most scanners can flag the package.
- A second execution path abuses npm’s github: dependency resolution to run a payload embedded in a forged commit inside a legitimate repository.
- Backdoors written to .vscode/tasks.json and .claude/settings.json survive after the malicious package is removed; secrets rotation alone is not enough.
- Now that the Shai-Hulud source code has been made public, the tooling is easier to reuse, increasing the risk of copycat attacks across the ecosystem.
The npm maintainer account atool (email: i@hust.cc) was compromised on May 19, 2026. That account controls 547 packages, the entire @antV data visualization suite plus widely used libraries like echarts-for-react, timeago.js, and size-sensor. Using automated tooling, attackers published 639 malicious package versions across 323 packages in under half an hour.
This wave has been linked to the ongoing Mini Shai-Hulud / TeamPCP campaign. Earlier waves targeted TanStack packages and SAP-related libraries. The @antV wave is the largest yet and introduces persistent backdoors in developer tooling that survive after the malicious package is removed.
Scope and Impact of the Attack
The compromised packages represent tens of millions of monthly downloads across frontend, charting, and data visualization ecosystems. These download figures illustrate package reach, not confirmed victim counts:
- size-sensor: 4.2M downloads/month
- echarts-for-react: 3.8M downloads/month
- @antV/scale: 2.2M downloads/month
- timeago.js: 1.15M downloads/month
Most projects use semver ranges in package.json (e.g., “echarts-for-react”: “^3.0.6”). npm resolves these to the highest matching version on each fresh install. The latest dist-tag was not updated on most packages, but that provides no protection as npm ignores it when resolving semver ranges.
How the Attack Works
Stage 1: Two-Path Delivery
Each compromised package received either of the two changes to package.json:
- A preinstall hook: “preinstall”: “bun run index.js”: runs a 498KB obfuscated JavaScript payload using the Bun runtime during every npm install.
- An optionalDependencies entry pointing to an orphan commit in the legitimate antvis/G2 repository:
“@antV/setup”:”github:antvis/G2#1916faa365f2788b6e193514872d51a242876569″
The commit was created via fork object sharing as the attacker forked the repo, pushed the payload as an orphan commit, and deleted the fork. Npm fetches the commit by SHA, runs a prepare script in it, and executes a second copy of the payload.
Both paths fire at install time. The second path bypasses security tools that only check the scripts.preinstall field.
Stage 2: Credential Harvesting
The payload scans the host environment for:
- GitHub tokens (PATs, Actions OIDC tokens) and npm publish tokens
- AWS credentials: environment variables, config files, and EC2 instance metadata (IMDSv2)
- GCP, Azure, and Kubernetes service account tokens
- HashiCorp Vault tokens, SSH keys, Docker credentials, and database connection strings
- Environment variables from 20+ CI/CD platforms (GitHub Actions, GitLab CI, Jenkins, CircleCI, and more)
On GitHub Actions runners, the payload scrapes Runner.Worker process memory via /proc/[pid]/mem, extracting secrets in plaintext that bypass GitHub’s log masking.
Stage 3: Exfiltration
Stolen data is AES-256-GCM encrypted and sent to t.m-kosche[.]com:443/api/public/otel/v1/traces, disguised as an OpenTelemetry telemetry endpoint. The payload also calls to GitHub, as it creates public repositories under the victim’s account with Dune-themed names (e.g., harkonnen-melange-742) and commits the stolen data there. More than 2,700 of these repositories have been observed. Each repository description contains the reversed text: “niagA oG eW ereH :duluH-iahS”, which decodes to “Shai-Hulud: Here We Go Again.”
Stage 4: Persistence
The payload writes backdoors to:
- .vscode/tasks.json : triggers silently when VS Code opens a project folder
- .claude/settings.json : triggers on every Claude Code session start
- .github/workflows/codeql.yml : Injected workflow that exfiltrates repo secrets
Both the VScode and Claude hooks re-download the Bun runtime and re-execute the payload. Removing the malicious package from package-lock.json does not remove these files. The payload may also attempt to install a background daemon named kitty-monitor (systemd service on Linux, LaunchAgent on macOS) that polls GitHub hourly for signed remote commands.
Stage 5: Worm Propagation
Using stolen npm tokens, the payload enumerates packages each token can publish, injects the preinstall hook and payload into those packages, and republishes them. One compromised developer account becomes the entry point for the next wave.
Why This Matters to DevOps and DevSecOps Teams
Detection-after-publish does not protect against install-time execution. preinstall hooks run during npm install before the package is fully unpacked and before most scanners can flag it. If a build resolves a malicious version during the gap between publish and detection, the payload has already run, and credentials have already left the environment.
The Bitwarden CLI compromise in April 2026 demonstrated the same gap; active for 1.5 hours, with build-time controls as the only effective barrier. The @antV wave was faster and reached a far larger package surface.
The persistence layer makes cleanup harder than previous waves. Teams that rotate secrets and remove the bad package may still have developer machines silently re-executing the payload through backdoored VS Code or Claude Code configurations. Cleanup requires auditing workstations, not just CI/CD pipelines.
TeamPCP has open-sourced the full Shai-Hulud framework on GitHub. The xinference PyPI compromise and the axios npm attack earlier this year show how this campaign style continues to expand across ecosystems and package types. Expect more waves from additional threat actors using the same tooling.
How InvisiRisk Protects Against This Attack
Stability Buffer
InvisiRisk’s Build Application Firewall (BAF) configurable Stability Buffer blocks package versions published within a configurable time window (default: 48 hours) from entering a build. The entire 639-version burst was published in under 30 minutes. Under the default Stability Buffer window, newly published versions from this burst would have been blocked automatically.
Build Proxy / Network Interception
InvisiRisk’s BAF operates as a network proxy inside CI/CD runners, intercepting all outbound traffic. C2 callbacks to t.m-kosche[.]com can be intercepted and blocked before data leaves the runner.
Unauthorized API Action Enforcement
InvisiRisk’s BAF enforces policy on outbound API calls during builds. POST and PUT operations that create repositories, push commits, or write data outside the expected build scope can be blocked. This stops the worm propagation step by blocking the API actions the worm relies on for propagation and GitHub dead-drop exfiltration.
Immediate Response Steps
If any of the affected packages entered your environment on May 19:
- immediately rotate all secrets accessible from the affected machine or runner.
- Audit .vscode/tasks.json and .claude/settings.json on developer workstations.
- Check affected systems for a kitty-monitor service or LaunchAgent.
Why Build-Time Defenses Matter
This wave confirms that a single compromised maintainer account in the npm ecosystem can achieve campaign-scale reach within minutes. The @antV packages are embedded in enterprise dashboards, analytics platforms, and frontend applications across the industry. The combination of install-time execution, persistent developer tooling backdoors, and an open-sourced attack toolkit makes this a threat that requires build-time defenses, not detection after the fact.


