Shai-Hulud Is Back: The npm Worm That Slipped Past npm's New Malware Scanner
A Shai-Hulud worm payload from May, with a hash researchers had published months ago, was republished in September and walked straight past npm's new malware scanner. It also writes itself into VS Code and Claude Code settings files.

On September 7, a single npm account published four small packages within the same hour: feishu-docx-mcp@0.3.2, bmc-i18n-extract-cli@1.1.1, blueai-cli@0.7.0, and bmc-translate-utils@1.1.1. All four carried the same malicious index.js payload, and all four went live on the registry without being blocked.
That payload was not new. Security firm Aikido, which spotted the packages, matched its SHA256 hash to a file first seen on May 19, during the Shai-Hulud outbreak in which a compromised maintainer account pushed 639 malicious @antv package versions in one hour. Aikido had recorded 319 package versions carrying that exact file, every one of them from May 19. Then the hash went quiet for 111 days, the longest dormancy-to-reactivation gap Aikido says it has seen, before someone republished it under new package names. npm's publish-time malware scanning, rolled out in July, let it straight through.
Why the scanner missed it
npm's publish-time scanning holds every new package for roughly five to fifteen minutes while an automated system checks it before it becomes installable. npm has not said how that system decides what is malicious: whether it uses signatures, static analysis, behavioral sandboxing, machine learning, or some mix of them.
Whatever it does, it did not catch a file that had been publicly documented, with its hash, for almost four months. Matching a hash is not hard. It is a lookup against a list of known-bad files, the same check antivirus software has done for decades. If publish-time scanning can't flag an exact copy of malware from the registry's own most recent worm outbreak, you should not count on it to catch anything newer.
What the payload actually does
The package declares a preinstall script, so it runs automatically during npm install, before you ever import it. That script runs bun run index.js. The payload looks for npm tokens on the machine and checks them against the registry to see which still work. With a working token, it lists the packages that account can publish, downloads their tarballs, injects the same payload, bumps the version number, and republishes. That is how one compromised developer becomes the next infection point.
The part that should get more attention from anyone using AI coding tools: the malware also writes to .vscode/tasks.json and .claude/settings.json. Both are ordinary config files that your editor and Claude Code read and trust. VS Code tasks can be set to run when a folder is opened, and Claude Code settings can define hooks that run shell commands during a session. A malicious entry in either file gives the attacker a way to run code again the next time you open the project, even after the npm package is gone.
The payload also talks to a hardcoded command-and-control domain (t[.]m-kosche[.]com) and mass-creates GitHub repositories with Dune-themed names and reversed "Shai-Hulud" strings in their descriptions, the same calling card earlier waves of this worm used.
Why this matters if you shipped with AI tools
Most apps built with Cursor, Lovable, Bolt, Replit, or Claude Code lean hard on npm. A prompt that says "add authentication" or "add a PDF export" can pull in a dozen new dependencies in seconds, and almost nobody reads through all of them before hitting install. That is exactly the ecosystem Shai-Hulud is built to exploit: fast, low-friction dependency additions, a wide blast radius, and a registry that still can't reliably catch malware it has already seen once.
Note that two of the four packages are small CLI and MCP tools, the kind of thing an AI assistant might suggest installing to "connect" to a service. If you or your assistant installed any package in the last few weeks, it is worth fifteen minutes to check.
What to check right now
Search your lockfiles and node_modules for the four package names:
grep -E "feishu-docx-mcp|bmc-i18n-extract-cli|blueai-cli|bmc-translate-utils" \
package-lock.json pnpm-lock.yaml yarn.lock bun.lock 2>/dev/null
ls node_modules | grep -E "feishu-docx-mcp|bmc-i18n-extract-cli|blueai-cli|bmc-translate-utils"Then look for the payload itself, since the same file may turn up under other package names. Its SHA256 is e37e3ddeeaaa9e0c4fdbcb829b4895a6521031c80053fc436625b61e6ee5b1a6:
find node_modules -name index.js -exec shasum -a 256 {} + 2>/dev/null \
| grep e37e3ddeeaaa9e0c4fdbcb829b4895a6521031c80053fc436625b61e6ee5b1a6Also open .claude/settings.json and .vscode/tasks.json in any project where you added a dependency recently. Look for hooks or tasks you did not add, especially anything that runs a shell command, calls bun, or reaches out to an unfamiliar domain. Check your DNS or proxy logs for the C2 domain above, and check your GitHub account for repositories you did not create.
If you find anything, treat every credential on that machine as compromised: npm tokens, GitHub personal access tokens, and any cloud keys in your environment variables. Rotate them. Deleting the package is not enough. Then check whether any package you maintain got a version bump you did not publish.
Going forward, installing with --ignore-scripts (or setting ignore-scripts=true in .npmrc) stops preinstall payloads like this one from running. A few packages that need a build step will break, and you can allow those one by one.
This is also a good reminder that dependency review is part of application security, not a separate concern. A vibe-coded app with clean code but a compromised dependency tree is still a compromised app. If you want a second set of eyes on what actually shipped in your project, VibeAudits offers a free assessment that checks your dependencies alongside the rest of your app's security posture.
VibeAudits audits apps built with Cursor, Lovable, Bolt, Claude Code, Replit, and other AI tools.