node_modules/.hooks/ directory
During npm lifecycle events for all packages
- Lives in
node_modules/.hooks/ directory- Attacker needs
- Filesystem write access
- Lifecycle
- InstallStartupyour codeModule loadRuntimeREPL
Runs while dependencies download and build. The app has never started.
How it runs
Obscure, and only relevant if you're somehow still on npm 6. npm supported a .hooks/ directory inside node_modules/ that could contain lifecycle scripts. Files there named preinstall, install, postinstall, preuninstall, or postuninstall get executed during the matching lifecycle events for all packages. It's a global lifecycle hook for the entire tree.
mkdir -p node_modules/.hooks
cat > node_modules/.hooks/postinstall <<'EOF'
#!/usr/bin/env sh
echo "*** node_modules/.hooks/postinstall ran ***"
EOF
chmod +x node_modules/.hooks/postinstall
npm install
If an attacker can write to node_modules/.hooks/postinstall, that script runs every time any package is installed. This worked in npm 6-era tooling but does not execute in modern npm (7+, including npm 10). Its obscurity cuts both ways. Most developers don't know it exists, so most security tools don't check for it.
Defend
Use npm 7+ (which ignores this mechanism); restrict write access to node_modules; scan for unexpected .hooks directories in legacy projects.