Give credit first: disabling install scripts by default was the right call, and it was years overdue.
On 8 July 2026, npm v12 shipped with lifecycle install scripts disabled by default — preinstall, install, postinstall no longer fire unless you opt in. GitHub described it as removing the ecosystem's largest code-execution surface, and that characterisation is fair. A very large share of npm supply chain attacks over the preceding two years had used exactly that hook.
Now the uncomfortable part. Three days later, the jscrambler compromise landed — and its later versions executed on import and CLI invocation rather than on install, explicitly defeating --ignore-scripts. The AsyncAPI hijack on 14 July did not need install scripts either.
The mitigation held for roughly 72 hours before live campaigns routed around it.
What --ignore-scripts actually protects
This is worth being precise about, because a lot of teams have been treating --ignore-scripts in CI as a supply chain control and it is much narrower than it sounds.
--ignore-scripts protects the moment of installation. That is it. It means a package cannot run arbitrary code merely by being placed on disk.
It does not protect:
- Import time. The instant your code does
require('thing')orimport 'thing', module top-level code runs. Anything at module scope executes with your process's full privileges and environment. - Build time. Webpack plugins, babel transforms, PostCSS plugins, bundler loaders — these exist to be executed by your build. A malicious webpack plugin does not need a lifecycle hook; running it is the point.
- Test time. Your test runner imports everything. Test environments frequently hold more credentials than production, and nobody watches their egress.
- CLI invocation.
npx thing, or a binary innode_modules/.bincalled from a package script. - Runtime. The package is in your production process. It runs.
For a build-time dependency — which describes most of what has been compromised recently, including jscrambler's bundler plugins and @asyncapi/generator — install-time execution was never necessary in the first place. Blocking it inconveniences the lazy attacker and nobody else.
Why single-vector mitigations decay so fast
The install-script hook was popular with attackers for one reason: it was the cheapest reliable execution primitive. When you remove the cheapest one, you do not remove the attacker's capability, you remove a line item from their cost sheet. They move to the next cheapest.
The ecosystem has a long list of execution primitives that are features, not bugs:
- npm/JS: module top-level code, bundler plugins, loaders, transforms,
binentries - Python:
.pthfiles that execute at interpreter startup,setup.py,conftest.py, import-time side effects - Both: anything a build tool is designed to execute
Any of these can be closed individually. None of them can be closed collectively, because "execute code the developer asked for" is the entire purpose of a build system.
That is the structural reason a defence built on enumerating vectors will always be a step behind. Attackers only need one open primitive; a vector-enumeration defence needs to have closed all of them.
What actually generalises
Three properties of a defence survive the attacker moving vectors:
1. It looks at behaviour, not at the entry point. A payload that harvests environment variables and posts them to a remote host looks the same whether it was triggered by postinstall, an import, or a webpack plugin. Detecting the payload's behaviour is vector-independent; detecting the trigger is not.
2. It removes the prize rather than the path. Every one of the July campaigns was after credentials sitting in developer environments and build pipelines. If there are no long-lived credentials resident in the environment, code execution yields dramatically less, regardless of how it was obtained. This is the highest-leverage control available and it is depressingly under-deployed.
3. It constrains egress. Execution without exfiltration is a much smaller problem. Nearly every payload in the July wave needed to reach an attacker-controlled destination — IPFS, a paste service, a GitHub repo, an Ethereum contract. Denying arbitrary egress from build environments breaks the campaign at its narrowest point.
None of these care which hook fired.
Do adopt npm v12 anyway
To be unambiguous: upgrade to npm v12 and keep install scripts off. It is free defence-in-depth, it eliminates a whole class of lazy attack, and there is no argument for leaving it on. Also set ignore-scripts=true in .npmrc so it survives tooling that pins older npm versions.
Just do not put it on the slide where you tell your board the supply chain is handled. It bought the ecosystem three days.
How Safeguard helps, at scale
Behavioural detection that is trigger-agnostic. Safeguard's Supply Chain Core runs eleven scanners against every version of every component and evaluates version-over-version behavioural deltas — new native binaries, new egress destinations, new persistence mechanisms, new environment-variable access patterns. That signal fires identically whether the code runs from a lifecycle hook, module top level, or a bundler plugin, and it fires before a CVE exists, which is the only timing that matters for a zero-day publish.
Lion: remove the prize. Lion is a 1B governance model that runs alongside AI coding agents and build tooling, enforcing just-in-time secret brokering instead of resident credentials, capability scoping, egress allowlists, and signed audit trails. Across a fleet of thousands of developer environments and CI runners, moving from standing credentials to brokered ones is the single change that most reduces the value of code execution to an attacker — and it is vector-independent by construction.
500K+ pre-vetted zero-CVE components. The strongest available posture is not to reach the compromised version at all. A curated component feed sidesteps the publish-to-detection window entirely, which is where every one of these campaigns lives.
Continuous SBOMs across build-time dependencies too. Most inventories under-count build-time and dev dependencies, which is precisely where these compromises land. Safeguard's continuous CycloneDX and SPDX SBOMs cover the full graph including transitive build tooling, so bundler plugins and generators are not a blind spot.
Griffin and Eagle for the response at org scale. When the next compromise lands, Eagle tells you which of your services were genuinely exposed rather than merely present in the tree (~80% fewer false positives; remediation from 45 days to 3), and Griffin authors and tests the pin-or-upgrade PRs across every affected repository simultaneously, through your existing merge gates. For an organisation with hundreds of repos, that is the difference between a day and a quarter.
The install-script change was good hygiene. Treat it as hygiene.
Sources: Tech Times: npm v12 blocking install scripts · GitGuardian: Four More Supply Chain Attacks Hit npm and PyPI · Unit 42: The npm Threat Landscape