DEV Community

Pico
Pico

Posted on • Originally published at getcommit.dev

637 npm Packages Compromised in 39 Minutes. The Malware Installs a Claude Code SessionStart Hook.

On May 19, 2026, between 01:39 and 02:18 UTC, a single compromised npm account published 637 malicious package versions across 323 packages. The entire attack took 39 minutes.

The packages included jest-canvas-mock (2.8M weekly downloads), echarts-for-react (1.1M), size-sensor (1.2M), timeago.js (295K), and most of the @antv visualization suite. Total blast radius: roughly 16 million weekly downloads.

This wasn't a human typing npm publish 637 times. This was a worm.

How the self-propagation works

The atool npm account was compromised (how is still unknown). That account had publish access to 547 packages. The initial payload did what you'd expect — harvested credentials from 80+ environment variables and 100+ file paths across AWS, GCP, Azure, GitHub, Kubernetes, and database systems.

Then it did something different: it searched for npm tokens with the bypass_2fa scope. In GitHub Actions environments, the malware exchanged OIDC tokens for per-package npm publish credentials. It then republished additional packages with itself embedded. An npm worm.

Two waves hit the registry. First: ~317 versions at 01:39. Second: ~314 versions 26 minutes later at 02:05. Detection started around 02:18.

The persistence mechanisms

The exfiltrated credentials are serialized as JSON, gzip-compressed, encrypted with AES-256-GCM, and wrapped with RSA-OAEP. The exfiltration channel disguises itself as OpenTelemetry traces, posting to t.m-kosche.com:443/api/public/otel/v1/traces.

A backup channel creates public repos under the victim's GitHub account, commits encrypted credential dumps with Dune-themed naming patterns.

Here's where it gets personal for anyone reading this on a machine with Claude Code installed:

The malware installs a SessionStart hook in .claude/settings.json. It also drops VS Code task automation in .vscode/tasks.json and a background daemon at ~/.local/share/kitty/cat.py that polls GitHub every 60 seconds for RSA-signed commands.

And there's a dead man's switch. If the stolen GitHub token gets revoked, the malware runs rm -rf ~/.

What the packages looked like before the attack

I scored the compromised packages using Commit, a behavioral supply chain scorer. The non-AntV packages — the ones most projects wouldn't think to audit — tell the clearest story:

Package Score Publishers Downloads/wk Risk
canvas-nest.js 50 1 1K WARN: no release 12+ months
timeago.js 65 2 295K WARN: no release 12+ months
size-sensor 66 1 1.2M HIGH: sole publisher + >1M/wk
echarts-for-react 71 1 1.1M HIGH: sole publisher + >1M/wk
jest-canvas-mock 72 2 2.8M WARN: no release 12+ months

Three of these five had a sole npm publisher. Two are stale — no release in over a year, still pulled by millions of projects weekly. That's exactly the profile that makes account takeover both easy and high-impact.

The @antv packages scored higher (84–89) because they have 17–18 maintainers. But that's exactly how the account worked: atool was one of those 18 maintainers. More publishers means more attack surface when any one of them can publish.

What to check

If your package-lock.json or yarn.lock includes any of these packages, check which versions you installed between 01:39 and 02:18 UTC on May 19.

Then check the rest of your dependency tree:

npx proof-of-commitment --file package-lock.json
Enter fullscreen mode Exit fullscreen mode

The packages that scored 50–72 before this attack — sole publishers, stale releases, high downloads — are the same profile that got compromised in the LiteLLM attack, the axios attack, and now this one.

The pattern doesn't change. The entry point is always the same: one compromised account with publish access to a widely-installed package.

What's different about this one

Previous supply chain attacks hit one package at a time. This one propagated. It turned compromised npm tokens into more compromised packages. The 39-minute window between first publish and detection is getting shorter, but the blast radius is getting wider.

And the persistence mechanisms are evolving. Targeting .claude/settings.json and .vscode/tasks.json means the malware survives container restarts and embeds itself in developer tooling — the exact environment where you make decisions about which packages to trust.

Run a supply chain audit on your project — or set up monitoring to get alerted when scores change.

Top comments (0)