npm: --ignore-scripts No Longer Enough, the Payload Fires at Import
The @asyncapi package compromise runs its payload at module load, not at install. Why --ignore-scripts fails and how to detect it with YARA.
For years, the standard advice to protect against malicious npm packages fit in a single flag: npm install --ignore-scripts. It disables lifecycle script execution, notably postinstall, the path most supply-chain compromise payloads travel through. npm 12 goes further and blocks these scripts by default. On July 14, 2026, Microsoft Threat Intelligence documented an attack that makes this advice obsolete: the compromise of the @asyncapi organization, where the payload does not run at install, but at module load.
This is a paradigm shift worth internalizing. --ignore-scripts only blocks install hooks. If the malicious code fires when a build, a continuous integration job, or an application first imports the module, none of those protections come into play. The payload wakes up at require or import, right in the middle of legitimate execution.
What was compromised
Microsoft reports that five package versions, across four names, were republished within roughly ninety minutes, each carrying the same injected loader. The affected packages are @asyncapi/specs (in both the 6.11.2-alpha.1 prerelease and the 6.11.2 stable release), @asyncapi/generator@3.3.1, @asyncapi/generator-components@0.7.1, and @asyncapi/generator-helpers@1.1.1. Together these packages total more than two million weekly downloads, which gives the measure of the exposed surface.
The attack leveraged trusted publishing pipelines in GitHub Actions, letting the malicious code slip through without relying on traditional npm install hooks. This is T1195.002 (Supply Chain Compromise: Compromise Software Supply Chain). The payload, related to the Miasma framework already seen in the Shai-Hulud attacks, combines credential theft, AI tool poisoning, worm-like propagation, and a multi-protocol command and control channel using HTTP, Nostr, IPFS, BitTorrent, libp2p, and Ethereum.
Why import-time execution changes detection
A postinstall payload runs once, at install time, in a relatively identifiable context. An import-time payload runs every time the module is loaded, inside the application process itself. That shifts the detection point: it is no longer about watching npm install, but about spotting abnormal behavior from a running Node.js process, and having signatures on the dropped artifacts.
The payload writes a sync.js file under %LOCALAPPDATA%\NodeJS\ for persistence, which falls under T1547, and performs bulk credential collection (T1555). These file artifacts are a solid anchor for a YARA rule, which stays valid even if the injection vector evolves.
A YARA rule on artifacts and C2 markers
The following rule targets the loader's characteristic strings: the persistence path, and markers for command and control channels atypical of a legitimate npm package. A code generation package has no reason to reference IPFS, Nostr, or an Ethereum address.
rule Npm_Asyncapi_ImportTime_Loader
{
meta:
description = "Detects the loader injected into compromised @asyncapi packages (July 2026)"
reference = "https://www.microsoft.com/en-us/security/blog/2026/07/15/unpacking-asyncapi-npm-supply-chain-compromise-import-time-payload-delivery/"
author = "ThreatClaw"
date = "2026-07-19"
strings:
$persist = "\\NodeJS\\sync.js" ascii wide nocase
$ipfs = "ipfs" ascii nocase
$nostr = "nostr" ascii nocase
$eth = "ethereum" ascii nocase
$b64eval = "Buffer.from" ascii
$childproc = "child_process" ascii
condition:
$persist or
( 2 of ($ipfs, $nostr, $eth) and $b64eval and $childproc )
}This rule fires either on the known persistence path, or on the improbable combination, in an npm module, of several exotic command and control protocols with dynamic code execution and child process spawning. The second branch aims to catch variants that would change the persistence file name but keep the C2 channel.
Complement with build-time detection
On the prevention side, a Nuclei-style scanner or a static inspection of the node_modules directory before build lets you flag the exact compromised versions. Pin your dependencies by exact version and integrity hash, and block the versions listed above in your dependency manager. Also watch for unexpected outbound connections from a continuous integration runner to IPFS addresses or Nostr relays, which have no business in a documentation-generator build pipeline.
The operational lesson is clear: the "install scripts are the only risk" mental model is out of date. A payload can lie dormant in the body of a module and wake at import, inside the most legitimate process there is. Detecting these threats requires maintained signatures on the real artifacts, not just rules on the npm lifecycle.
That is the purpose of the ThreatClaw YARA feed: detection rules built from real samples, tested against a corpus of legitimate code to limit false positives, and refreshed at the pace of supply-chain compromises.
Related articles
IronWorm hides a Rust binary triggered at preinstall, harvests cloud and AI keys, then self-propagates via GitHub. Here is the YARA rule to detect it.
A rule feed is not worth its rule count. It is worth the proof that the rules fire and what you do when they trigger. Tested on real engines, false-positive-proven, signed, and every rule ships an investigation playbook wired to our other engines.
From June to August 2026, ThreatClaw forged 72 new malware families into its YARA feed: Prometei, WannaCry, EternalBlue, GhostNFC, SpyNote, Mamont, Neshta… each tested against 5,694 legitimate binaries, zero false positives.
Prometei botnet on Linux, Windows code injection, Office macros, Android, downloaders. ThreatClaw adds 14 fresh malware families to its YARA feed, 391 rules, tested against 5,694 legitimate binaries with zero false positives.