ISSUE 17 · 6 MIN READ ·

The 'npm install' that ran a stranger's code on your machine

A single npm install can run someone else's script the moment you type it — no import, no run. How to turn off install scripts and check you're covered.

A developer on your team adds a package they need, types npm install, and goes to make a coffee. By the time they’re back, the install has finished and everything looks normal. What they didn’t see is that one of the packages — or something buried deep in its dependencies — ran a script of its own the instant it was installed. Not when the code was imported. Not when the app was run. At install time, automatically, with the full run of the developer’s machine and whatever credentials sit on it.

This is the quiet failure, and it catches careful people. You can read every line of a library before you import it and still be too late, because the dangerous moment was npm install, before you’d run a single thing yourself. Some of the biggest supply-chain attacks of 2026 leaned on exactly this hook. In June, an attacker used a forgotten contributor account to republish more than 140 packages in the popular @mastra scope, each carrying a look-alike dependency whose install script fired the moment it landed — no import required.

Why it stays invisible

npm packages can define lifecycle scripts — commands named preinstall, install and postinstall that npm runs for you as part of installing the package. They exist for good reasons: some libraries genuinely need to compile a native component or run a small setup step. The trouble is that these scripts run automatically and silently, with the same permissions as the person doing the install, and almost nobody turns them off.

So the mental model most developers carry — “I’ll review the code before I use it” — quietly doesn’t apply. Reviewing protects you at import time and run time. Install-time scripts fire earlier than either, on nothing more than the act of adding or updating a dependency. And because a modern project pulls in hundreds of transitive dependencies nobody has read, the malicious script rarely lives in the package you chose; it lives in something that package depends on, three or four levels down, that got compromised last week. The install succeeds, the output scrolls past, and the one line that mattered looks exactly like the thousand that didn’t.

Find it yourself

You can check whether your machines are exposed, and close the gap, in about ten minutes. The check is a single command.

  1. Open a terminal and run:

    npm config get ignore-scripts
  2. If it prints false (the default), lifecycle scripts are allowed to run on install — the exposed state. If it prints true, they’re already blocked.

  3. Check the project as well as your user account — a project-level .npmrc can override your global setting. From the project folder, run the same command, and open any .npmrc file in the project root to see what it sets.

No command line, or not a developer yourself? This one lives with whoever writes or builds your software — your dev team, contractor, or agency. Ask them two direct questions: “Do we set ignore-scripts to true?” and “Do our builds use npm ci rather than npm install?” If the answer to either is no or “not sure,” the gap above is open. You don’t need to follow the technical detail to ask the question.

The fix

The fix is to stop trusting packages to run code on you by default, without making day-to-day work painful.

Turn install scripts off by default. Set ignore-scripts=true in your .npmrc so no package runs install-time code automatically. Commit this at the project level so every developer and every build inherits it, rather than relying on each person to configure their own machine.

Handle the few packages that genuinely need a script. Some native modules really do need to build on install. When ignore-scripts is on and one of those fails, you’ll find out immediately — and can then run its build step deliberately and knowingly for that one package, which is the whole point. A rare, visible, intentional exception beats a permanent, invisible, blanket permission.

Use npm ci in your builds. For continuous-integration and deployment, npm ci installs strictly from the lockfile, which makes builds reproducible and removes a class of surprise. Pair it with ignore-scripts so an automated build isn’t the thing that quietly runs a stranger’s code with your deployment credentials.

Pin and slow down updates. Attackers rely on you pulling the newest version within hours of a compromise. A short delay before adopting brand-new releases, and a lockfile you actually review, buys time for a bad package to be caught and pulled.

None of this needs a security team — it’s one setting, committed once, plus a habit of asking before a dependency gets to run code on your behalf. The reassuring part is how cheap it is: the same ten-minute check protects every install that comes after it.

Check your inbox — confirm and you're in. Latest issue: The Remote Desktop you opened "just for now" is still open.

One real, fixable exposure every week. Free.