Aller au contenu
Français
medium

Shai-Hulud is back: an npm worm loots the AntV ecosystem

Lire cet article en français

8 min read
Shai-Hulud

One morning, going through my supply chain watch, one name came up twice, from two independent sources: Shai-Hulud. I first took it for yet another poisoned npm package, the kind of incident that goes by every week. Reading the details, I understood it was on another scale entirely.

On 19 May 2026, a new wave of this npm worm compromised dozens of packages in the AntV ecosystem, Alibaba's data visualisation suite. Not an obscure package: libraries downloaded millions of times a week. And the worm does not merely steal, it propagates, reusing the tokens it takes.

This post covers what I understood of the modus operandi, the detail that made me wince as a daily Claude Code user, and the first thing I did: check my own package-lock.json.

What surfaced in my watch

Two articles published the same day described the same incident from two angles. StepSecurity published a detailed technical analysis of the payload and its runtime behaviour. Sonatype took a step back with a title that says it all: maintainer accounts remain the easy target.

When two serious sources converge within hours on a subject, it is no longer background noise. It is an active incident worth stopping for.

The account behind the leak is anything but anonymous. The npm account atool publishes timeago.js, 1.5 million weekly downloads, and belongs to the AntV maintenance team. The @antv namespace groups more than 100 packages for charting, mapping and rendering. One compromised account, and a whole slice of the JavaScript ecosystem becomes a vector.

The modus operandi

What struck me was the discipline of the attack. It does not look like a cobbled-together script.

The malicious packages were published in two coordinated waves, at 01:56 then 02:06 UTC. Ten minutes apart, a minor version bump each time. The goal is plain: make the publications look like routine updates and guarantee that at least one delivery channel works, whatever the victim's environment.

To execute, the worm uses two mechanisms triggered by a simple npm install. The first is classic: a preinstall or postinstall script in package.json. The second is craftier: an optionalDependencies entry pointing at a poisoned commit in the legitimate antvis/G2 GitHub repository. That commit's prepare hook fires the payload. Since no scripts entry appears in the npm archive itself, this mechanism slips past static analysis tools that inspect published packages.

The payload is an obfuscated JavaScript file of nearly 500 KB, executed by the Bun runtime, silently installed on the fly if absent.

What the payload takes

This is where the incident changes category. The payload is not a simple npm token stealer. It goes after anything that looks like a secret.

Its most worrying capability targets GitHub Actions runners. The code reads the memory of the Runner.Worker process directly, through /proc/[pid]/mem, to extract CI/CD secrets in clear text. Log masking, which many teams rely on, is strictly useless against this: the secret is read before being masked.

It does not stop at pipelines. The payload sweeps more than 130 file paths on the machine: AWS, GCP and Azure credentials, ~/.kube/config files, HashiCorp Vault tokens, ~/.npmrc, ~/.docker/config.json, private SSH keys, cryptocurrency wallets, mail cookies.

Stolen data leaves through two channels. The primary one is a relay repository through the GitHub API, inside antvis/G2 itself. The fallback is a command server, t.m-kosche.com, disguised as an OpenTelemetry collector, a clever choice since many organisations let observability traffic through unfiltered.

The worm uses what it steals

Most supply chain attacks stop at exfiltration. Shai-Hulud does not. It uses the stolen tokens.

By the time researchers were writing their analysis, more than 2,500 public GitHub repositories had already been created with stolen tokens, each named with terms from the Dune universe. That number is not anecdotal: it gives a lower bound on the number of genuinely compromised environments. Every repository created means at least one token that worked.

It is this loop, steal tokens, use them to publish and create infrastructure, repeat, that justifies the word worm. The attack has no clean beginning and end; it feeds itself. That is what makes it one of the largest documented npm supply chain campaigns to date.

The first thing I did

Before writing anything, I checked my own ground. This blog runs on Astro, therefore on npm, with a few hundred dependencies. One command is enough to settle the question:

Fenêtre de terminal
grep -E '"@antv|echarts-for-react|jest-canvas-mock|jest-date-mock|timeago' package-lock.json

No result. Across the 905 dependencies of my package-lock.json, no compromised package, since an Astro Starlight site has no need for AntV visualisation libraries. Relief, but a reflex worth keeping: that check takes ten seconds and should become automatic on every alert of this kind.

If the command does return something, the reasoning changes entirely. You must then consider every secret reachable from that environment compromised, and rotate them immediately: GITHUB_TOKEN, NPM_TOKEN, cloud keys, Vault tokens, SSH keys. Until a token is revoked, treat it as an open door.

How to protect yourself

Beyond the one-off check, this incident is a good excuse to harden npm installs durably. Four measures genuinely change the odds.

Pin versions. Install with npm ci from a lockfile, with no floating version ranges. A floating version means accepting the next malicious package in advance.

Disable install scripts. npm install --ignore-scripts, or ignore-scripts=true in .npmrc, neutralises preinstall and postinstall hooks, precisely Shai-Hulud's first mechanism.

Enforce a quarantine period. Refuse versions published less than a few days ago. A malicious version is usually detected and pulled quickly; waiting means letting the community take the hit for you.

Harden CI runners. Outbound network control and detection of runner memory reads block exfiltration. In StepSecurity's tests, those two protections are exactly what stopped the attack.

What I take away

  • Shai-Hulud is a self-replicating npm worm; the 19 May 2026 wave targeted the AntV ecosystem, but the pattern will repeat.
  • Compromise triggers on a plain npm install, through a preinstall hook or a booby-trapped optionalDependencies that escapes static analysis.
  • The payload steals CI/CD secrets in clear text, over 130 credential files, and plants backdoors in Claude Code and VS Code.
  • Checking your package-lock.json takes ten seconds: make it a reflex on every alert.
  • The durable measures are well known: pin, --ignore-scripts, quarantine, runner hardening. This incident merely proves they are no longer optional.

Sources