Cómo blindar npm ante ataques a la cadena de suministro con allowScripts y min-release-age
Índice de contenidos
- Puntos clave
- Qué hizo ChainDrop y por qué estas dos opciones lo frenan
- Cómo aprobar scripts de instalación con allowScripts
- Cómo hacer que el CI falle con un script sin revisar
- Cómo esperar unos días antes de instalar con min-release-age
- Qué más conviene activar
- Preguntas frecuentes
- ¿Qué versión de npm necesito para allowScripts y min-release-age?
- ¿allowScripts sustituye a --ignore-scripts?
- ¿Cuántos días pongo en min-release-age?
- Conclusión
- Fuentes
npm 11.19 trae dos defensas contra paquetes comprometidos: allowScripts, que exige aprobar los scripts de instalación de cada dependencia, y min-release-age, que ignora las versiones publicadas hace menos de N días. Activadas juntas habrían frenado el gusano ChainDrop de agosto de 2026, que se propagaba con un preinstall y versiones de parche nuevas.
npm 11.19 trae dos defensas que cubren las dos vías por las que entró el gusano ChainDrop en agosto de 2026: allowScripts exige aprobar los scripts de instalación de cada dependencia y min-release-age ignora las versiones publicadas hace menos de N días. Las he probado el 7 de octubre de 2026 en la imagen node:26-slim, que trae npm 11.19.1, con paquetes reales del registro. Aquí tienes cómo activarlas, qué escriben en tu proyecto y qué pasa en el CI cuando aparece un script sin revisar. También hay una versión en inglés.
Puntos clave
- ChainDrop comprometió más de 400 paquetes de npm, según Microsoft. Se ejecutaba en un hook
preinstally se propagaba publicando versiones de parche nuevas con el malware dentro. allowScriptses un campo depackage.json. npm avisa de cada dependencia con scripts de instalación no aprobados, y constrict-allow-scriptsla instalación falla conESTRICTALLOWSCRIPTS.npm approve-scripts esbuildañadió{"esbuild@0.28.2": true}alpackage.json: la aprobación queda fijada a esa versión, así que una versión nueva vuelve a pedir revisión.- Con
allowScripts: {"bcrypt": false}, bcrypt 6.0.0 se instaló sin ejecutar su script y siguió funcionando, porque carga binarios precompilados. - Con
min-release-age=7,npm i viteinstaló la 8.3.1, del 24 de septiembre, en lugar de la 8.3.3, publicada el día anterior.
Qué hizo ChainDrop y por qué estas dos opciones lo frenan
El 4 de agosto de 2026, el equipo de seguridad de Microsoft publicó el análisis de ChainDrop[1], un gusano que afectó a más de 400 paquetes de npm de editores sin relación entre sí. Robaba credenciales de npm, GitHub, AWS, Kubernetes y Vault, y se replicaba solo. Microsoft lo describe así: "After obtaining an npm publishing token, it enumerates packages available to the compromised identity, downloads their latest tarballs, inserts the malware and setup loader, adds a preinstall hook, increments the patch version, and republishes the modified packages."
Esa frase contiene las dos condiciones que necesita el ataque:
- Que se ejecute un script de instalación. Sin el hook
preinstall, el código malicioso se descarga pero no corre durantenpm install. - Que instales la versión recién publicada. Una versión de parche nueva entra en cualquier rango
^1.2.3en el siguientenpm installo en cuanto Renovate la propone.
allowScripts ataca la primera y min-release-age, la segunda. Ninguna sustituye al lockfile ni a npm ci, que siguen siendo la base: si no lo tienes claro, empieza por comparar Dependabot y Renovate para controlar cuándo entran las actualizaciones.
Cómo aprobar scripts de instalación con allowScripts
Instala una dependencia con scripts y npm te avisa. Este es el aviso que dio npm i esbuild en npm 11.19.1:
npm warn install-scripts 1 package has install scripts not yet
covered by allowScripts:
npm warn install-scripts esbuild@0.28.2 (postinstall: node install.js)
npm warn install-scripts Run `npm install-scripts ls` to review,
or `npm install-scripts approve <pkg>` to allow.
El aviso no bloquea: el script se ejecutó igual. Para revisarlos todos, npm install-scripts ls lista los paquetes pendientes con el comando exacto que ejecutarían. Si el script es legítimo, apruébalo:
npm approve-scripts esbuild
node -p "JSON.stringify(require('./package.json').allowScripts)"
La segunda línea imprimió {"esbuild@0.28.2":true}. La aprobación se guarda con la versión porque allow-scripts-pin vale true por defecto: cuando esbuild publique la 0.28.3, npm volverá a avisar. Ese es justo el caso de ChainDrop, una versión de parche nueva de un paquete que ya tenías aprobado.
Para denegar un script, escribe false en el campo. npm deny-scripts solo funciona con paquetes ya instalados; antes de instalar, edita el package.json:
{
"allowScripts": {
"esbuild@0.28.2": true,
"bcrypt": false
}
}
Con esa política, npm i bcrypt@6.0.0 instaló el paquete sin ejecutar su script install y require('bcrypt').hashSync siguió siendo una función, porque bcrypt 6 trae binarios precompilados y el script solo los busca. No todos los paquetes nativos son así: si uno necesita compilar en la instalación, denegarlo lo deja roto.
Cómo hacer que el CI falle con un script sin revisar
En el CI quieres un error, no un aviso. Añade strict-allow-scripts=true al .npmrc del proyecto o pásalo en la línea de comandos. Al instalar bcrypt sin cubrirlo en la política, npm se detuvo con este error:
npm error code ESTRICTALLOWSCRIPTS
npm error --strict-allow-scripts: 1 package(s) have install
scripts not covered by allowScripts:
npm error bcrypt@6.0.0 (install: node-gyp-build)
Las dependencias denegadas con false se saltan en silencio y no rompen el modo estricto. La única forma de saltarse la política es --dangerously-allow-all-scripts, que la documentación define como una vía de escape para migrar y desaconseja expresamente. --ignore-scripts sigue teniendo prioridad sobre todo lo anterior.
Cómo esperar unos días antes de instalar con min-release-age
min-release-age le dice a npm que construya el árbol solo con versiones publicadas hace más de N días. Ponlo en el .npmrc:
min-release-age=7
min-release-age-exclude[]=@miorg/*
strict-allow-scripts=true
La segunda línea exime los paquetes de tu organización, que quieres usar en cuanto los publicas. Lo probé con Vite, que había publicado la 8.3.2 el 1 de octubre y la 8.3.3 el 6 de octubre. Sin la opción, npm i vite instaló la 8.3.3; con --min-release-age=7 instaló la 8.3.1, del 24 de septiembre.
La espera es una ventana de detección: da tiempo a que el registro y los editores detecten y retiren una versión comprometida antes de que llegue a tu árbol. El precio es que una corrección de seguridad legítima también tarda una semana en llegarte, salvo que la instales a mano con una versión exacta.
Qué más conviene activar
Las dos opciones encajan con medidas que ya existían:
npm cien el CI: instala exactamente lo que dice el lockfile y falla si no coincide conpackage.json- Tokens de publicación granulares o publicación con OIDC: reducen lo que puede hacer un token robado, que es como ChainDrop saltaba de un editor a otro
npm audit signatures: comprueba las firmas del registro y la procedencia de los paquetes que la publican- Revisión de los PR de dependencias: con SLSA como marco si quieres formalizar la cadena
Preguntas frecuentes
¿Qué versión de npm necesito para allowScripts y min-release-age?
Las dos están en npm 11.19, la que traen las imágenes oficiales de Node 24 y Node 26 en octubre de 2026. npm 12 también las incluye. Comprueba la tuya con npm -v y npm config ls -l | grep -E "allow-scripts|min-release-age".
¿allowScripts sustituye a –ignore-scripts?
Lo mejora. --ignore-scripts desactiva todos los scripts, también los que necesitas; allowScripts te deja aprobar uno a uno y bloquear el resto.
¿Cuántos días pongo en min-release-age?
Siete es un punto de partida razonable y es el ejemplo de la documentación. Menos de tres días deja poco margen para que se detecte un paquete malicioso; más de 14 retrasa demasiado las correcciones.
Conclusión
ChainDrop necesitaba dos cosas: que su preinstall se ejecutara y que instalaras una versión de parche recién publicada. npm 11.19 te deja negarle las dos con dos líneas en .npmrc y un campo en package.json, y en mi prueba las dos hicieron lo que prometen.
El siguiente paso es ejecutar npm install-scripts ls en tu proyecto, aprobar lo que reconozcas, denegar el resto y activar strict-allow-scripts y min-release-age=7 en el CI.
Fuentes: [1] ChainDrop supply chain compromise, Microsoft Security Blog[1], [2] Configuración de npm: allow-scripts, strict-allow-scripts y min-release-age[2], [3] npm approve-scripts[3], [4] npm 11.19.0, notas de la versión[4], [5] npm audit signatures[5].
Fuentes
Código fuente
Accede a todo el código fuente de este artículo en GitHub.
Ver en GitHub