Yes, a single npm install can compromise your business: package installation scripts run automatically on the developer's machine or the CI server, with its privileges, its environment variables and its secrets. A malicious package — or a legitimate one whose maintainer was hacked — can steal credentials, cloud keys or tokens in seconds, without a single line of your own code being at fault. The good news: simple measures drastically reduce this risk.
Why the open source ecosystem became a prime target
Attacking a well-defended company is expensive. Compromising an open source package used by thousands of projects opens thousands of doors at once. Three mechanisms dominate:
- Installation hooks: npm automatically executes the preinstall and postinstall scripts declared by each dependency. That is arbitrary code running on your machine at install time, before any review.
- Typosquatting: packages with names nearly identical to popular libraries, counting on a typo or an approximate AI suggestion to get installed.
- Compromised maintainers: an npm account hijacked through phishing allows publishing a booby-trapped version of a perfectly legitimate package, automatically pulled by every project that accepts minor updates.
Very real precedents
Public cases abound. In 2018, event-stream, downloaded millions of times a week, was booby-trapped after an attacker earned the maintainer's trust in order to target cryptocurrency wallets. In 2021, ua-parser-js shipped a cryptominer and a password stealer after its author's account was hijacked. In 2025, the compromise of some of the ecosystem's most downloaded packages through the phishing of a maintainer, followed by the self-replicating Shai-Hulud worm that stole npm tokens to spread from package to package, showed the phenomenon changing scale. Every wave follows the same pattern: one link of trust gives way, and the attack travels down the chain to your machines. None of these packages were obscure: they sat in the dependency trees of countless production applications, which is precisely what made them attractive targets.
Field report: the preinstall.js that was not meant to be seen
During a project takeover for a client, our team applied a systematic reflex: never run npm install on an unknown repository before inspecting its installation scripts. The package.json declared a preinstall hook pointing to a preinstall.js file. First warning sign: nothing in the project justified such a hook.
Second, subtler signal: the file looked short and harmless, but its size in bytes did not match its visible content at all. Hexadecimal analysis revealed the trick: thousands of Unicode variation selectors — characters invisible on screen — encoded a payload hidden inside what looked like a plain configuration file. Once decoded in an isolated environment, without ever executing it, the payload turned out to be an infostealer: harvesting browser credentials, crypto wallets and environment variables.
A telling detail about the level of sophistication: the malware did not contain its command server's address in plain text. It retrieved it by reading a transaction on a public blockchain used as a dead drop — a rendezvous point impossible to take down, which the attacker can update by publishing a new transaction. The team neutralized the hook, purged the history, rotated every potentially exposed secret and verified that no workstation had run the install. Cost of detection: one hour of vigilance. Cost avoided: the compromise of an entire team's credentials.
The best practices that change everything
Block automatic execution
- Configure --ignore-scripts by default (in the project's or the machine's .npmrc): installation scripts then only run explicitly, after review.
- Before any project takeover or new dependency, inspect the preinstall, install and postinstall hooks in package.json and the files they reference. Treat any dependency's installation script as untrusted code, because that is exactly what it is until proven otherwise.
Lock and audit versions
- Always commit the lockfile and install with npm ci in CI: you run exactly the reviewed versions, not the latest publication of a possibly hijacked maintainer. This single habit protects you from the most common scenario: a compromised version published minutes ago and pulled automatically into your next build.
- Audit dependencies regularly (npm audit, software composition analysis tools) and be wary of recent, rarely downloaded packages or names close to well-known libraries.
- Avoid immediate automatic version bumps: leaving a few days between a release and its adoption gives the ecosystem time to detect compromises.
Isolate and compartmentalize
- Run CI in disposable, sandboxed environments, with minimally scoped, short-lived secrets.
- On developer workstations, limit what a script can steal: a secrets manager rather than well-stocked .env files, npm tokens with reduced privileges, MFA on registry accounts.
Suspected compromise: the first moves
- Disconnect the machine from the network without wiping it, to preserve evidence.
- Treat every secret reachable from that workstation as stolen: npm and cloud tokens, SSH keys, browser passwords, active sessions. Revoke and rotate them immediately.
- Identify the offending package and version through the lockfile, and check which other machines or pipelines installed it.
- Monitor the access logs of critical services over the exposure window.
The software supply chain has become an attack surface in its own right, and no team can audit thousands of transitive dependencies alone. Putting the right guardrails in place, then knowing how to react fast the day a doubt appears, is a specific skill — the kind a technical partner experienced in malicious code analysis and incident response brings.
