npm v12 Turned Off Install Scripts. The AsyncAPI Attackers Ran Their Payload at Import Time Instead. Here's the 15-Minute Sunday Audit.
npm v12 Turned Off Install Scripts. The AsyncAPI Attackers Ran Their Payload at Import Time Instead. Here's the 15-Minute Sunday Audit.
npm v12 shipped this month with the biggest security redesign in the tool's 16-year history: install scripts, Git dependencies, and remote sources are now off by default. That's a real fix. postinstall hooks were the delivery mechanism for most of the past year's supply-chain worms: Shai-Hulud, the North Korea-linked campaigns, the whole genre. Killing them by default closes the door those attacks walked through.
Then, on July 14, Microsoft Threat Intelligence published a breakdown of the @asyncapi org compromise, where the attackers didn't use an install script at all. They moved the payload to import time. The malicious code ran when your app called require or import on the package, which is to say, when it actually ran. npm v12's headline defense does nothing about that, and if you read the v12 release notes and quietly concluded npm was "fixed," this is the paragraph that should change your Sunday plans.
Install-time and import-time are two different doors
The mental model most people carry is "malicious package runs bad code when I npm install it." That's install-time execution, and it's what postinstall scripts enabled. It's dangerous because it fires on every developer machine and every CI runner that installs the dependency, before you've written a line of code against it. npm v12 blocking install scripts by default is aimed squarely at this.
Import-time execution is the other door. Instead of running during installation, the payload runs at module load: the moment your code imports the package. In the @asyncapi case, Microsoft described five package versions across four package names republished within roughly 90 minutes, each carrying the same injected loader that executed on import. Days earlier, on July 11, the jscrambler packages were hit in a similar coordinated push, with hidden native binaries executing during installation or use.
The distinction matters because your defenses are asymmetric. Turning off install scripts stops the first door cold. It does not touch the second. Code that runs on require is, from the runtime's perspective, just the package doing its job. There's no separate lifecycle hook to disable, because importing a module is supposed to run the module.
Why "I pinned my versions" isn't the whole answer
The standard advice after any npm scare is pin your versions and commit your lockfile. Do that: it's necessary. But notice how these July attacks actually worked: attackers used stolen publishing credentials to republish trusted, widely-used packages. If you pin to a version and that exact version gets compromised and republished, or if you're pulling a range that resolves to a poisoned release, pinning alone won't save you. The trust you're relying on is the maintainer's account, and that's exactly what got stolen.
That's the part that makes this a genuinely harder problem than "don't install sketchy packages." The @asyncapi and jscrambler packages weren't sketchy. They were legitimate, popular dependencies whose publishing credentials were compromised. You can't out-vet your way around a trusted package turning hostile after the fact.
The 15-minute Sunday audit
Here's what I'd actually do this weekend. None of it is heroic; all of it narrows the blast radius when (not if) a dependency you trust gets republished with a payload.
First, confirm your npm is v12 and install scripts are off. Run npm --version. If you're on v12, the default is scripts-off; verify you haven't re-enabled them globally, and check your CI config for a stray --foreground-scripts or a config that turns them back on. If you're not on v12 yet, add ignore-scripts=true to your .npmrc now and treat the upgrade as this week's job.
Second, commit and pin your lockfile, and use npm ci in CI rather than npm install. npm ci installs exactly what the lockfile specifies and fails if package.json and the lockfile disagree, no silent range resolution to a freshly poisoned release.
Third, and this is the one that addresses import-time specifically, constrain what your build and runtime can reach on the network. Import-time payloads generally want to phone home: exfiltrate an env var, pull a second stage, hit a webhook. In CI, restrict egress so a compromised dependency can't reach an arbitrary host. Locally, be suspicious of any dependency that opens network connections or reads credentials at load. You won't catch everything, but you turn a silent exfiltration into a failed connection you can see.
Fourth, audit which dependencies actually execute meaningful code on import versus which are inert until you call a function. A pure-function library that does nothing at load is lower risk than one that spins up a client, reads config, or touches the filesystem on require. You don't have to eliminate the latter; you have to know where they are, so that when a CVE lands you know your exposure in minutes instead of hours.
Fifth, turn on whatever your host offers for provenance. npm's signed provenance and package attestations won't stop a credential-theft republish on their own, but they raise the cost and give you a signal when something's off. Free, five minutes, worth it.
The honest take
The narrative that "npm finally fixed supply-chain security" is half true, and the half that's false is the dangerous half. Blocking install scripts by default is the most consequential change npm has made in years, and it retires an entire category of attack. But attackers adapt to the door you lock by using the one you didn't, and the @asyncapi compromise is the proof-of-concept that import-time delivery is that door. If your entire post-v12 posture is "scripts are off, we're good," you've patched last year's attack and left this month's wide open. The fix isn't complicated and it isn't a product you buy: it's an afternoon of pinning lockfiles, restricting egress, and knowing which of your dependencies run code the instant you import them. That's a solo-operator-sized job. Do it before Monday.
Author
Lukas
@lukcombinatorSources
- Unpacking the AsyncAPI npm supply chain compromise and import-time payload delivery — Microsoft Security Blog
- Active Exploitation Alert: Jscrambler npm Packages Compromised — Rescana
- npm v12 Ships This Month, Blocking Install Scripts — TechTimes
- The npm Threat Landscape: Attack Surface and Mitigations — Unit 42