- Seattle Times evitó por casualidad el ataque Shai-Hulud 2.0, pero partiendo de la premisa de que la suerte no puede ser una estrategia de seguridad, implementó defensas del lado del cliente
- Las mejoras de npm como Trusted publishing / provenance / tokens granulares refuerzan el lado de la “publicación”, pero sigue existiendo un vacío: no impiden la ejecución de código malicioso al momento de “instalar o actualizar”
- pnpm usa el mismo registro de npm, pero agrega controles que dificultan la ejecución de paquetes maliciosos en la etapa de consumo (install/update)
- En el piloto aplicaron 3 controles de pnpm, diseñados para bloquear por separado vectores como la ejecución de scripts de ciclo de vida, la instalación inmediata del release más reciente y la degradación de confianza
- Las excepciones no se consideran una falla, sino parte del diseño; el objetivo es operar con defense-in-depth, documentando las excepciones mientras el resto de las capas sigue protegiendo
Contexto del incidente y premisas
- En noviembre de 2025 ocurrió un caso en el que un gusano autorreplicante en npm infectó 796 paquetes y se propagó a una escala de 132 millones de descargas mensuales
- El ataque utilizó scripts preinstall para robar credenciales, instalar puertas traseras persistentes e incluso, en algunos entornos, borrar el entorno de desarrollo
- La razón por la que no afectó a nuestra organización no fue una defensa sólida, sino la casualidad de no haber ejecutado
npm install ni npm update durante el periodo del ataque
- En una organización de noticias, la confianza es clave; una intrusión en la cadena de suministro puede exponer datos de clientes, credenciales de empleados, infraestructura de producción y código fuente, además de implicar altos costos de recuperación y notificación
El equipo y el contexto de adopción
- Seattle Times ha usado npm durante mucho tiempo como gestor de paquetes principal, y aunque hubo experimentos con Yarn, no se consolidaron
- La razón para adoptar pnpm es contar con controles de seguridad del lado del cliente que complementen las mejoras a nivel de registro
- Consideraron que la transición era viable porque pnpm es un drop-in replacement que usa el mismo registro, los mismos comandos y el mismo flujo de trabajo
- No es un caso de estudio terminado, sino una compartición de los problemas y el proceso de razonamiento de un equipo real que apenas está comenzando con la seguridad de la cadena de suministro
Por qué se necesitan controles del lado del cliente
- Las mejoras de seguridad en npm sí han hecho más difícil publicar paquetes maliciosos tras comprometer una cuenta
- Estas mejoras protegen el lado de la “publicación (publishing)”, pero no impiden por sí mismas la instalación de paquetes maliciosos durante la etapa de “consumo (consuming)”
- En
npm install y npm update, los lifecycle scripts (preinstall/postinstall, etc.) pueden ejecutar código arbitrario con permisos del desarrollador antes de evaluar la seguridad del paquete
- Esos scripts pueden acceder a credenciales de npm/GitHub/AWS/DB, al código fuente, a la infraestructura en la nube y a todo el sistema de archivos
- Ataques como Shai-Hulud explotan esta estructura; si la cuenta del maintainer es comprometida, el script malicioso puede ejecutarse en el momento de la instalación, causando daño antes de que la comunidad lo detecte
- La idea es combinar las mejoras del lado de publicación de npm con los controles del lado de consumo de pnpm como defensas complementarias, formando una estrategia de “defense-in-depth”
Las 3 capas aplicadas
- En el piloto se usaron juntos 3 controles, cada uno orientado a un vector de ataque distinto
- Cada control tiene una vía de escape para excepciones realistas y fue diseñado asumiendo que en entornos reales esas excepciones serán necesarias
Control 1: Gestión de Lifecycle Scripts
- pnpm puede bloquear por defecto los lifecycle scripts, permitiendo que la instalación continúe con advertencias
- Como existía la preocupación de que las advertencias fueran ignoradas, se eligió
strictDepBuilds: true para forzar que, si hay scripts, la instalación falle de inmediato
- Un ejemplo de configuración en
pnpm-workspace.yaml se compone de los siguientes campos
strictDepBuilds: true
onlyBuiltDependencies: lista de paquetes permitidos que sí requieren scripts de build
ignoredBuiltDependencies: lista de paquetes con scripts de build innecesarios para bloquear (o ignorar)
- Los “scripts necesarios” se definen como acciones como compilar extensiones nativas o enlazar librerías dependientes de la plataforma
- Los “scripts innecesarios” se definen como optimizaciones o configuraciones opcionales que, según la forma de uso del equipo, no afectan la funcionalidad
- Hacer fallar la instalación fuerza los siguientes pasos
- pnpm indica claramente qué paquetes tienen scripts
- investigar y entender qué hace cada script
- decidir conscientemente, con criterio humano, si se permite o se bloquea, y documentarlo
- El equipo de pnpm está considerando hacer
strictDepBuilds: true el valor por defecto en v11 y también evalúa mejorar los nombres de la sintaxis allow/deny
Control 2: Release Cooldown
- Las versiones publicadas recientemente no pueden instalarse durante un periodo de enfriamiento, dando tiempo a la comunidad para detectar y retirar paquetes maliciosos
- Un ejemplo de configuración en
pnpm-workspace.yaml se compone de los siguientes campos
minimumReleaseAge: <duration-in-minutes>
minimumReleaseAgeExclude: lista de excepciones permitidas, como hotfixes urgentes
- Hace falta abandonar el hábito de pensar que “lo más nuevo es lo mejor” y adoptar la idea de que, desde la perspectiva de la cadena de suministro, algo un poco más viejo puede ser más seguro
- En el ataque de septiembre de 2025 (16 paquetes, incluidos debug y chalk), los paquetes fueron eliminados en unas 2.5 horas; en Shai-Hulud 2.0 de noviembre de 2025 tomó unas 12 horas
- Según la tolerancia al riesgo de cada organización, el cooldown puede ser de horas, días o semanas, y en cualquiera de esos casos habría bloqueado ese ataque
- Además encaja bien con la realidad de muchas organizaciones, que de por sí no siempre usan la versión más reciente, por lo que el cooldown no interfiere demasiado con el trabajo
- Cuando realmente se necesita, como en un parche de seguridad o un bug crítico, puede levantarse la restricción como excepción tras una revisión
Control 3: Política de confianza
- Se bloquea la instalación si aparece una versión publicada con una autenticación más débil que la de una versión anterior
- Se explica como una señal de que la cuenta del maintainer pudo haber sido comprometida y que la publicación se hizo desde la máquina del atacante, en lugar del CI/CD oficial
- Un ejemplo de configuración en
pnpm-workspace.yaml se compone de los siguientes campos
trustPolicy: no-downgrade
trustPolicyExclude: lista de excepciones permitidas, como migraciones de CI/CD
- Se explica que npm rastrea 3 niveles de confianza para la publicación de paquetes (de más fuerte a más débil)
- Trusted Publisher: basado en GitHub Actions + OIDC + npm provenance
- Provenance: attestation firmada desde CI/CD
- No Trust Evidence: publicación basada en username/password o token
- Si una nueva versión tiene un nivel de confianza menor que la anterior, la instalación falla
- En el ataque de agosto de 2025 de s1ngularity, donde el atacante publicó una versión maliciosa localmente sin acceso al CI/CD y por eso no había provenance, este control habría bloqueado la instalación
- Como casos legítimos de degradación se mencionan la incorporación de un nuevo maintainer, una migración de CI/CD o un hotfix manual durante una caída del CI/CD; tras investigarlo, se agrega a la lista de excepciones
- Esta función es una característica nueva añadida a pnpm en noviembre de 2025, y todavía se está aprendiendo con qué frecuencia ocurren degradaciones legítimas
Ejemplo de cómo se combinan las capas: parche de una vulnerabilidad de React
- En un escenario en el que hay que aplicar de inmediato el parche de una vulnerabilidad crítica en React Server Components, divulgada en diciembre de 2025
- Normalmente el cooldown impediría instalar una versión “recién publicada”, pero al tratarse de un parche crítico de seguridad no se puede esperar
- En ese caso, se agrega la versión específica de React a
minimumReleaseAgeExclude, pero solo después de revisar el aviso de vulnerabilidad y la legitimidad del parche
- Aunque se aplique esa excepción, las demás capas siguen protegiendo
- React normalmente no tiene lifecycle scripts, así que si el parche agrega scripts, eso sería una señal inmediata de sospecha y podría bloquearse
- Si un atacante roba credenciales y publica el “parche” localmente, podría bloquearse por degradación del nivel de confianza
- La excepción no se trata como una “falla de seguridad”, sino como un diseño en el que, aunque una capa se omita, las demás siguen ahí y se elimina el punto único de falla
Resultados del piloto
- Se hizo una PoC aplicando los 3 controles a un servicio backend
- El tiempo total de preparación, incluyendo investigación, comprensión y definición del enfoque, fue de unas pocas horas
- pnpm identificó 3 paquetes con lifecycle scripts
- esbuild: optimiza el arranque del CLI a nivel de milisegundos, pero el equipo solo usa la API de JS, así que se consideró innecesario
- @firebase/util: hace configuración automática del SDK del cliente, pero el equipo solo usa el SDK del servidor, así que se consideró innecesario
- protobufjs: realiza verificación de compatibilidad de esquemas, pero solo se usa como dependencia transitiva y se consideró innecesario para este caso de uso
- Tras revisar la documentación y analizar los scripts (incluyendo apoyo de IA para interpretarlos), concluyeron que los tres scripts no eran necesarios para su caso de uso y los bloquearon
- No hubo impacto funcional
- La fricción es una característica intencional: un mecanismo para dejar de confiar implícitamente en el código que se ejecuta en el entorno
- Si una nueva dependencia trae scripts, estiman que la revisión y documentación tomarán unos 15 minutos
Aprendizajes operativos
- La combinación de capas del lado del cliente con las mejoras del lado de publicación de npm demuestra que defense-in-depth sí funciona en la práctica
- Incluso si se aplica una excepción, sigue habiendo otras capas activas, lo que reduce la ansiedad respecto a esas excepciones
- Cambiar el modelo mental de “primero la conveniencia” a “primero la seguridad” toma tiempo, pero una vez adoptado se siente natural
- Es aplicable de forma práctica incluso en organizaciones medianas sin un equipo de seguridad dedicado
- La trust policy es una función con apenas unas semanas de existencia, así que todavía falta aprender más sobre la frecuencia de degradaciones legítimas y su operación real
- En el corto plazo planean expandirlo a otros codebases, lo que permitirá reunir más datos en aplicaciones con grafos de dependencias distintos
Consejos de implementación para otros equipos
- Se recomienda empezar con un solo proyecto para aprender el flujo de trabajo y los puntos de fricción
- Como pueden ser necesarias excepciones en lifecycle scripts, release cooldown y trust downgrade, conviene diseñar desde el inicio asumiendo esas excepciones
- Se recomienda usar
strictDepBuilds: true desde el primer día para forzar la instalación fallida, en lugar de depender de advertencias
- Documenta todas las excepciones para dejar rastro de auditoría y facilitar su limpieza futura
- Recuerda que una excepción en una capa no elimina la protección de las demás
1 comentarios
¡pnpm! ¡pnpm! ¡pnpm! Como era de esperarse, sigo confiando en él.