Una guía pragmática para no caer en el siguiente Axios, TanStack o Shai-Hulud.

Seguro que en los últimos meses has visto pasar por tu timeline más de un caso preocupante: Axios comprometido en marzo, Shai-Hulud reventando paquetes a discreción, los SAP packages a finales de abril, y hace literalmente unos días, los 42 paquetes de TanStack. Y no, no es casualidad ni mala suerte: es la nueva normalidad del ecosistema npm.
Me parece que a estas alturas ya no podemos permitirnos seguir haciendo npm installcon los ojos cerrados. Lo bueno es que las defensas existen, están documentadas y, en su mayoría, son baratas de aplicar. Lo malo es que casi nadie las aplica de forma sistemática.
Vamos a verlas una a una.
Antes que nada: cómo entran
Aunque cada ataque tiene su matiz, los vectores se repiten. Básicamente son tres:
- Secuestro de credenciales del mantenedor. Phishing, RAT en el equipo del mantenedor, una cuenta sin 2FA… y de repente alguien publica una versión “legítima” envenenada. Es lo que pasó con Axios.
- Typosquatting. Publicas un paquete con un nombre casi idéntico al original (
axoisinstead ofaxios) y esperas que alguien se equivoque. Suena tonto? Sí, pero también funciona. - Dependency confusion. Publicas en el registry público un paquete con el mismo nombre que uno interno de una empresa, y el resolutor de npm tira del público porque tiene una versión más alta.
El denominador común es siempre el mismo: la resolución automática de versiones durante npm install . El código se ejecuta antes de que nadie lo haya mirado.
A lo que nos lleva esto es que toda nuestra estrategia de defensa va a girar alrededor de controlar qué versiones entran y cuándo se ejecutan.
Defensa 1: entiende los rangos de SemVer (de verdad)
Esto es lo más básico y, paradójicamente, lo que más gente ignora. Una mirada rápida a tu package.json:
1.12.0→≥1.12.0, <2.0.0. Cualquier parche o minor entra automáticamente. Cómodo, pero peligroso.~1.12.0→≥1.12.0, <1.13.0. Solo parches. Más razonable.1.12.0→ la versión exacta y nada más. La opción aburrida y la opción correcta para producción.
No voy a entrar en detalle en por qué SemVer existe, pero sí en cuál es el coste real: si pineas exacto, un atacante que publique una versión maliciosa nueva no toca tu build. Punto. Esa única decisión, multiplicada por todas tus dependencias, te quita gran parte del problema (aunque también te priva de actualizaciones automáticas).
Defensa 2: el lockfile es sagrado (y “npm ci”, también)
Si pineas versiones pero no usas el lockfile correctamente en CI, no has avanzado nada. Aquí hay una diferencia que mucha gente pasa por alto:
npm installpuede actualizar el lockfile si encuentra discrepancias. Útil en local. Un disparate en CI.npm cirespeta el lockfile de forma estricta y falla si algo no cuadra. Es lo que quieres en tu pipeline.
npm ciEso sí: el lockfile no es una bala de plata. Si alguien ya consiguió que el atacante meta una versión envenenada y tú regeneras el lockfile sin querer, el lock se “envenena” también. Por eso esta defensa necesita acompañarse de la siguiente.
Defensa 3: enfriamiento de versiones (cooldown)
Esta es de las que más me gustan, porque es barata y eficaz. La idea es sencilla: no instales versiones publicadas hace menos de X días.
¿Por qué funciona? Porque la mayoría de los ataques se detectan en horas. El de Axios estuvo vivo unas 4–5 horas. El de TanStack se cazó en 20 minutos. Si tu CI hubiera estado configurado para ignorar publicaciones de menos de una semana, ni te habrías enterado.
Desde la versión 11.10.0 de npm, en tu .npmrc:
min-release-age=7dY si usas Dependabot, ya tiene cooldown nativo desde julio de 2025:
cooldown:
default-days: 7
semver-minor-days: 3
semver-patch-days: 3Sí, perderás 7 días de “estar al día”. Me parece un precio absurdamente bajo a cambio de no participar de oficio en el siguiente incidente.
Defensa 4: vigilar `install scripts` como un cancerbero
Casi todos los ataques modernos a npm se apoyan en lo mismo: un postinstall o un preinstall que se ejecuta automáticamente cuando instalas el paquete. Es el agujero de la baldosa floja.
Lo razonable es que tu pipeline marque y obligue a revisión manual cualquier PR que introduzca una dependencia nueva con hasInstallScript: true. No hace falta una herramienta cara: con un pequeño script que parsee el package-lock.json ya tienes el 80% del problema cubierto.
Si quieres ir un paso más allá, puedes directamente lanzar npm ci --ignore-scripts en CI y permitir scripts solo para una whitelist explícita. Es más restrictivo, no obstante, te ahorra muchísimos disgustos.
Defensa 5: telemetría y firmas
Aquí entran las medidas que ya existen y que poca gente activa:
npm auditen cada build. No es perfecto, pero es gratis.npm audit signaturespara verificar provenance (las firmas OIDC de los Trusted Publishers de npm).- Bloquear a nivel de DNS o firewall la infraestructura C2 ya conocida de campañas activas. Por ejemplo, en el caso Axios se publicó la IP
142.11.206.73y el dominiosfrclack.com. Si tu red los bloquea, un eventual RAT que cayera en tu CI no podría llamar a casa. - Integrar una herramienta SCA que cruce tus dependencias con bases de datos de paquetes maliciosos conocidos.
Ninguna de estas medidas por separado te salva. Todas juntas te dan una red de protección razonable.
Defensa 6: un registry interno como puerta de entrada
Esta es la jugada de las organizaciones que se lo toman en serio. La idea es que nadie en tu empresa hable directamente con registry.npmjs.org. Todo pasa por un proxy interno (Verdaccio, Artifactory, Nexus, lo que sea) con tres reglas:
- Solo se pueden instalar paquetes públicos previamente aprobados.
- Los paquetes internos viven ahí y no se pueden sobrescribir desde fuera (adiós al dependency confusion).
- El registry interno aplica cooldown, escaneo y caché.
Es más esfuerzo, sí. Pero si tu empresa tiene una superficie grande de Node, es la única forma de no jugártela cada semana.
Unas palabras sobre la “fatiga”
Puede ser tentador, y a todos nos ha pasado, pensar que esto es exagerado. Que estamos hablando de probabilidades bajas. Que tu proyecto “no es objetivo”. Que ya tienes bastante con sacar features.
El problema es que el coste de aplicar la mayor parte de estas defensas es ridículo:
- Pinear versiones: cinco minutos y un commit.
npm ciinstead ofnpm installen CI: una línea.- Cooldown de 7 días: una línea en
.npmrc. - Detección de install scripts en PRs: un script de 30 líneas.
Y el coste de no aplicarlas, cuando toca, es despertarse un martes y descubrir que tus credenciales de AWS, GCP y GitHub llevan tres horas viajando hacia un servidor en Lituania.
Cierre
No tengo soluciones mágicas, y dudo que nadie las tenga. La supply chain de npm es lo que es: un ecosistema gigantesco, abierto, con miles de mantenedores voluntarios y un modelo de confianza implícito que se está rompiendo en directo.
Lo que sí podemos hacer es asumir que cualquier paquete puede comprometerse en cualquier momento y trabajar desde esa premisa. Pinear, pinear lockfiles, retrasar adopción, vigilar scripts, controlar tráfico saliente del CI. Nada de esto es nuevo, nada es revolucionario, y precisamente por eso es tan frustrante que tantos equipos sigan sin aplicarlo.
Espero que os haya servido el repaso. Saludos y hasta la próxima 😉

