Table of contents

Mini Shai-Hulud Hits @antv: 323 npm Packages Compromised Through the atool Maintainer Account

Mini Shai-Hulud Hits @antv: 323 npm Packages Compromised Through the atool Maintainer Account - Mini Shai Hulud is Back 1

An active supply chain attack has compromised 323 npm packages published under the atool npm maintainer account. The wave sweeps the entire @antv data-visualization organization alongside standalone libraries with wide independent adoption: echarts-for-react, timeago.js, size-sensor, and canvas-nest.js. With echarts-for-react pulling roughly 1.1 million weekly downloads, any project that auto-updates these packages is in scope. The attack follows the established Mini Shai-Hulud pattern: once a maintainer account is compromised, every package it can reach gets a malicious version in the same wave.

What’s new in this wave

This wave follows the established Mini Shai-Hulud pattern of large-scale malicious publishing through a compromised maintainer account. Volume is the point, pushing hundreds of packages at once to obscure individual bad releases inside normal registry traffic. What’s new is the account that got hit.

atool is the publishing identity for @antv, a foundational charting and data-visualization stack used across dashboards and analytics tools built in React and Vue, plus a cluster of standalone libraries with substantial independent adoption. The affected set spans the major @antv packages (g2, g6, x6, l7, s2, f2, g, g2plot, graphin, data-set) alongside echarts-for-react, timeago.js, size-sensor, and canvas-nest.js. echarts-for-react in particular sits as a direct dependency in a large number of React projects, most of which don’t pin to an exact version.

Technical analysis

What changed in the malicious releases

Three modifications appear consistently across the compromised versions.

Obfuscated index.js at the package root

The malicious release injects a single-line, ~490KB file built around a 1,728-entry string table with hex-encoded variable names:

const _0x5d6bea=_0x1169;(function(_0x3187cf,_0x895a8e){const _0x5f2282=...

preinstall Hook

The malicious package adds a preinstall lifecycle hook:

"preinstall": "bun run index.js"

npm runs preinstall automatically at install time, before application code is imported or tests run. The attacker calls bun run rather than node, sidestepping script-scanning tools that key on Node.js invocations.

@antv/setup Dependency pin advanced

The pinned git reference in optionalDependencies shifts from 7cb42f57561c321ecb09b4552802ae0ac55b3a7a to 1916faa365f2788b6e193514872d51a242876569, pulling in attacker-controlled code as a second stage alongside the root-level payload.

Obfuscation

The payload uses three layers. The string table uses a custom base64 alphabet with lowercase characters before uppercase, reversing the standard order. A self-executing function at startup rotates the table by 227 positions to the correct offset.

Configuration values including the C2 domain, exfiltration paths, and environment variable names are encrypted using PBKDF2-SHA256 key derivation at 200,000 iterations with 32-byte output, feeding a custom substitution cipher built on Fisher-Yates-shuffled permutation tables applied in three rounds.

Execution and persistence

On first run, the payload forks a detached child process with __DAEMONIZED=1 set in the environment and exits the parent immediately. The preinstall hook appears to complete normally while the child continues running in the background. A PID lock file at <tmpdir>/tmp.0987654321.lock prevents concurrent instances.

The string table contains .claude/setup.mjs, .vscode/setup.mjs, and the invocation bun run .claude/, pointing to Claude Code and VS Code local configuration directories as persistence targets. This is also why bun is a hard requirement on the target machine beyond the initial preinstall hook. The payload includes an installTokenMonitor function, indicating ongoing token monitoring rather than a one-shot collection pass.

For CI runner environments, the payload contains:

echo 'runner ALL=(ALL) NOPASSWD:ALL' > /mnt/runner && chmod 0440 /mnt/runner

which would create passwordless sudo access for the runner user.

Credential collection

The payload targets cloud and infrastructure credentials including AWS credentials, GitHub tokens, GitHub Actions OIDC tokens, HashiCorp Vault tokens, and Kubernetes secrets.

It also targets developer machine files such as SSH keys, package registry tokens, .env files, password managers, shell history, and Claude Code configuration.

For CI environments, masked GitHub Actions secrets are read directly from runner process memory via a getLinuxDumpScript function using:

tr -d '\0' | grep -aoE '"[^"]+":{"value":"[^"]*","isSecret":true}'

against:

/proc/<pid>/mem

Exfiltration

Collected data is sent over two channels.

The primary channel is an HTTPS POST to:

t[.]m-kosche[.]com

at path:

/api/public/otel/v1/traces

a path that mimics an OpenTelemetry traces endpoint, the kind of outbound HTTPS traffic that appears in virtually every modern cloud application’s network logs and rarely triggers alerting.

The User-Agent is hardcoded to:

python-requests/2.31.0

to avoid detection logic that keys on Node.js as the runtime.

The secondary channel uses collected GitHub tokens to commit stolen data via the GitHub API, using:

fix: ci

as the commit message.

Indicators of compromise

Network

IndicatorValue
C2 domaint[.]m-kosche[.]com
Exfiltration path/api/public/otel/v1/traces
Spoofed User-Agentpython-requests/2.31.0
IMDS probehxxp://169[.]254[.]169[.]254/latest/meta-data/iam/security-credentials/
ECS metadata probehxxp://169[.]254[.]170[.]2

Filesystem

PathDescription
<tmpdir>/tmp.0987654321.lockPID lock file written at startup
.claude/setup.mjsClaude Code persistence path string in payload
.vscode/setup.mjsVS Code persistence path string in payload

Process / Behavioral

IndicatorDescription
__DAEMONIZED=1 env varSet on the detached child process
bun run index.js in preinstallActivation hook in package.json
/proc/<pid>/mem read + isSecret grepIn-memory secrets scraping on Linux runners
fix: ci commit messageGitHub exfiltration commits

Package

IndicatorValue
Malicious @antv/setup commit ref1916faa365f2788b6e193514872d51a242876569
Previously clean @antv/setup commit ref7cb42f57561c321ecb09b4552802ae0ac55b3a7a

What to do

  1. Audit the dependency tree. Run:
npm ls

to surface transitive pulls. echarts-for-react and any @antv/* package are the primary targets, but transitive exposure through other libraries is common.

  1. Pin to known-good versions. Roll back to the last release published before this wave and lock it in package-lock.json or pnpm-lock.yaml.
  2. Rotate all credentials on any machine that ran:
npm install

against a flagged version. Execution happens at install time through the preinstall hook; a machine is compromised whether or not the affected package was ever imported.

Rotate npm and PyPI tokens, GitHub tokens, AWS credentials, Vault tokens, Kubernetes service account tokens, SSH keys, and any secrets stored in .env files or shell history.

  1. Check for persistence artifacts. Inspect:
<tmpdir>/tmp.0987654321.lock
.claude/setup.mjs
.vscode/setup.mjs

On CI runners, audit the sudoers configuration for:

runner ALL=(ALL) NOPASSWD:ALL

entries under:

/etc/sudoers.d/
/mnt/runner
  1. Block automatic upgrades for @antv/* and the related packages until the legitimate maintainer confirms a clean release line. Disable preinstall and postinstall script execution in CI with:
npm install --ignore-scripts

as a durable defense against this class of attack.

Conclusion

This wave is the established Mini Shai-Hulud playbook applied to a higher-profile account. The techniques – broad credential collection, CI targeting, persistence, security-tool evasion – are consistent with prior waves. What changes is the reach: compromising the atool account puts the entire @antv ecosystem and several high-download standalone libraries in scope at once.

Mend.io coverage

Mend.io has issued five MSC advisories covering all 323 affected packages:

  • MSC-2026-5740
  • MSC-2026-5736
  • MSC-2026-5737
  • MSC-2026-5738
  • MSC-2026-5739

Manage open source risk

Recent resources

Mini Shai-Hulud Hits @antv: 323 npm Packages Compromised Through the atool Maintainer Account - Mend securing RubyGems

Inside the RubyGems Supply Chain Attack: How Mend Defender Caught a Coordinated Flood Before It Spread

How Mend.io caught a coordinated RubyGems attack and what it teaches us.

Read more
Mini Shai-Hulud Hits @antv: 323 npm Packages Compromised Through the atool Maintainer Account - Mini Shai Hulud is Back

Mini Shai-Hulud Is Back: 172 npm and PyPI Packages Compromised in Latest Wave

Shai-Hulud's largest wave: 172 npm and PyPI packages compromised in 48 hours.

Read more
Mini Shai-Hulud Hits @antv: 323 npm Packages Compromised Through the atool Maintainer Account - npm supply chain attack

PhantomRaven Wave 5: New Undocumented NPM Supply Chain Campaign Targets DeFi, Cloud, and AI Developers

33 malicious NPM packages target DeFi, cloud, and AI developer credentials.

Read more