Una defensa simple contra los ataques a la cadena de suministro de npm
Cada vez que ejecutas npm install, estás confiando en código de cientos de paquetes que nunca has leído. Esa confianza es exactamente lo que los atacantes explotan. En el gusano Shai-Hulud de septiembre de 2025, scripts maliciosos que se ejecutaban durante la instalación recolectaron credenciales de forma silenciosa y republicaron paquetes comprometidos a gran escala, todo antes de que el prompt regresara. El compromiso de Axios en marzo de 2026 siguió el mismo patrón: las versiones maliciosas (1.14.1 y 0.30.4) incorporaban una dependencia con un hook postinstall que ejecutaba un payload de acceso remoto.
No necesitas herramientas pesadas para reducir esta exposición. Un solo cambio de configuración aborda directamente el vector de ataque más común.
Puntos clave
- Los scripts del ciclo de vida de npm (
preinstall,install,postinstall,prepare) se ejecutan automáticamente con todos los privilegios del usuario, lo que los convierte en la vía de menor fricción para los ataques a la cadena de suministro. - Establecer
ignore-scripts=trueen.npmrcbloquea el vector de ataque más común con una sola línea de configuración. - Los paquetes con binarios nativos como
sharp,bcryptysqlite3aún pueden compilarse selectivamente usandonpm rebuilddespués de una instalación sin scripts. - Una verificación en CI sobre
package-lock.jsonpara detectar nuevas entradas"hasInstallScript": trueseñala las dependencias que requieren revisión antes de fusionarse. - Combinar
ignore-scripts=trueconmin-release-age=7añade un retraso que ayuda a evitar versiones de paquetes recién comprometidas.
Por qué los scripts del ciclo de vida son el riesgo principal
Cuando instalas un paquete npm, Node ejecuta automáticamente cualquier script definido en su package.json: preinstall, install, postinstall y prepare. Estos hooks se ejecutan con todos tus privilegios de usuario, otorgando acceso a ~/.ssh, ~/.aws/credentials, los tokens de ~/.npmrc y a todas las variables de entorno de tu shell.
Sin solicitud de confirmación. Sin sandbox. Solo ejecución silenciosa.
En el ataque a Axios, una dependencia transitiva maliciosa llamada plain-crypto-js utilizó un hook postinstall para depositar un troyano de acceso remoto. En el ataque de suplantación de la CLI de Bitwarden, un hook preinstall lanzó un payload ofuscado de gran tamaño que recolectaba credenciales en la nube e intentaba propagarse a través del acceso de publicación en npm de la víctima.
La seguridad de los scripts del ciclo de vida importa porque representa la vía de ataque de menor fricción en todo el panorama de seguridad de dependencias de JavaScript.
La defensa principal: npm ignore-scripts
Añade una línea al .npmrc de tu proyecto:
ignore-scripts=true
Esto le indica a npm que omita todos los scripts del ciclo de vida durante la instalación. Un hook postinstall malicioso simplemente no se ejecuta.
Para una instalación puntual sin modificar .npmrc:
npm install --ignore-scripts
Advertencia importante: Esta no es una solución completa. El ataque a Bitwarden registró un binario malicioso a través del campo bin en package.json, lo que significa que el payload aún se ejecutaba cuando un usuario invocaba el comando bw, incluso con --ignore-scripts activo. Ninguna bandera por sí sola elimina todo el riesgo.
Qué se rompe y cómo manejarlo
Algunos paquetes legítimos requieren scripts del ciclo de vida para compilar binarios nativos. Los más comunes incluyen sharp, bcrypt, sqlite3, canvas y puppeteer (que descarga Chromium).
Verifica si tu proyecto utiliza alguno de ellos:
npm ls --all | grep -E "(sharp|bcrypt|sqlite3|canvas|puppeteer|node-gyp)"
Si obtienes coincidencias, recompila solo esos paquetes específicos después de instalar:
npm install --ignore-scripts
npm rebuild sharp --ignore-scripts=false
npm rebuild bcrypt --ignore-scripts=false
Este enfoque mantiene los scripts deshabilitados globalmente, permitiendo selectivamente la compilación de los paquetes que has revisado explícitamente.
Discover how at OpenReplay.com.
Detectar nuevos scripts de instalación antes de que se ejecuten
En lugar de reaccionar después de los hechos, marca las nuevas dependencias con scripts de instalación antes de que se fusionen. Añade esta verificación a tu pipeline de CI:
# Detect newly added packages with install scripts in PRs
if git diff origin/main -- package-lock.json | \
grep -E '^\+\s*"hasInstallScript": true' > /dev/null; then
echo "⚠️ New install script detected—manual review required"
exit 1
else
echo "✅ No new install scripts"
fi
En el ataque a Axios, plain-crypto-js era una dependencia totalmente nueva con un hook postinstall. Esta verificación lo habría señalado antes de que se fusionara el PR.
Para más detalles sobre cómo npm registra los scripts de instalación en los lockfiles, consulta la documentación oficial de package-lock.json.
Lo que estas defensas no cubren
Sé honesto sobre los límites:
- Los lockfiles no previenen ataques si un desarrollador ejecutó
npm installdurante la ventana de compromiso y envenenó el lockfile antes de que lo detectaras. npm auditsolo detecta paquetes maliciosos conocidos; los ataques nuevos no aparecerán en su base de datos.- 2FA en cuentas de npm no protege a los consumidores de un paquete legítimamente publicado por un mantenedor comprometido.
--ignore-scriptsno bloquea código malicioso incrustado en el propio JavaScript de runtime del paquete.
Un enfoque por capas ayuda. Combina ignore-scripts=true con min-release-age=7 en .npmrc (requiere npm v11.10.0 o posterior) para evitar instalar paquetes publicados en la última semana, la ventana en la que la mayoría de los ataques están activos y sin detectar. Esta configuración está documentada en la documentación de configuración de npm bajo min-release-age.
Empieza aquí
Añade esto a tu .npmrc hoy mismo:
ignore-scripts=true
min-release-age=7
Luego añade la verificación de CI para nuevas entradas hasInstallScript en cada PR que toque package-lock.json. Estos dos cambios abordan el patrón de ataque utilizado en los recientes ataques a la cadena de suministro de npm, sin requerir nuevas herramientas, servicios pagos ni cambios significativos en el flujo de trabajo.
Conclusión
No puedes leer cada línea de cada paquete del que dependes, pero sí puedes limitar de forma drástica lo que esos paquetes pueden hacer durante la instalación. Deshabilitar los scripts del ciclo de vida por defecto, recompilar solo los paquetes nativos en los que confías y señalar las nuevas entradas hasInstallScript en CI neutralizan, en conjunto, el vector de ataque más explotado en el ecosistema npm. Ninguna de estas medidas requiere nuevos proveedores ni presupuesto: solo unas pocas líneas en .npmrc y una única verificación en CI. Adóptalas hoy y el próximo ataque al estilo Shai-Hulud tendrá un impacto mucho menor en tu proyecto.
Preguntas frecuentes
Puede romper paquetes que compilan binarios nativos durante la instalación, como sharp, bcrypt, sqlite3, canvas y puppeteer. Después de ejecutar npm install con los scripts deshabilitados, usa npm rebuild seguido del nombre del paquete con --ignore-scripts=false para compilar solo los paquetes confiables que has revisado. La mayoría de las dependencias puramente de JavaScript funcionarán sin ningún cambio.
No. Bloquea el vector más común—los hooks del ciclo de vida como postinstall—pero no detendrá código malicioso en el JavaScript de runtime de un paquete ni un binario malicioso registrado a través del campo bin. El ataque de suplantación de la CLI de Bitwarden eludió ignore-scripts de esta manera. Combínalo con min-release-age, revisión del lockfile y auditoría de dependencias para una defensa por capas.
La configuración min-release-age requiere npm versión 11.10.0 o posterior. Verifica tu versión con npm --version y actualiza si es necesario usando npm install -g npm@latest. Verifica que la configuración esté activa usando npm config get min-release-age después de configurarla.
Confirma tu archivo .npmrc con ignore-scripts=true en el repositorio para que CI herede la configuración automáticamente. Para builds que realmente necesiten compilación nativa, añade comandos npm rebuild explícitos (con --ignore-scripts=false) para los paquetes específicos requeridos. Esto mantiene el valor predeterminado seguro mientras documenta exactamente qué paquetes están autorizados a ejecutar código de instalación.
Gain control over your UX
See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay. — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.