4 puntos por GN⁺ 2025-12-15 | 3 comentarios | Compartir por WhatsApp
  • El paquete malicioso de npm Shai-Hulud 2.0 infectó la máquina de un desarrollador y robó el acceso a la organización de GitHub de Trigger.dev
  • La infección comenzó cuando el desarrollador ejecutó pnpm install y se activó el script preinstall del paquete malicioso, usando la herramienta TruffleHog para robar credenciales
  • Durante 17 horas, el atacante clonó 669 repositorios y luego, durante 10 minutos, intentó hacer force-push a 199 ramas y cerrar 42 PR
  • Los paquetes y los sistemas de producción no fueron comprometidos, y el ataque fue detectado en 4 minutos, tras lo cual se bloqueó el acceso a la cuenta
  • Después del incidente, se reforzó la seguridad con medidas como desactivar scripts de npm, actualizar a pnpm 10, publicación de npm basada en OIDC y protección de ramas aplicada en todos los repositorios

Resumen del ataque

  • El 25 de noviembre de 2025, mientras se depuraba internamente en Slack, se detectó una anomalía: varios repositorios recibieron commits de “init” a nombre de Linus Torvalds
  • La investigación confirmó que el gusano de cadena de suministro Shai-Hulud 2.0 infectó la máquina de un desarrollador y robó credenciales de GitHub
  • Se informó que este gusano infectó más de 500 paquetes de npm y afectó a más de 25,000 repositorios
  • Los paquetes oficiales de npm de Trigger.dev (@trigger.dev/*, CLI) no fueron infectados

Línea de tiempo del ataque

  • 24 de noviembre, 04:11 UTC: comenzó la distribución del paquete malicioso
  • 20:27 UTC: se infectó la máquina de un desarrollador en Alemania
  • 22:36 UTC: primer acceso del atacante e inicio de la clonación masiva de repositorios
  • 15:27~15:37 UTC (25 de noviembre): se ejecutó el ataque destructivo durante 10 minutos
  • 15:32 UTC: se detectó la anomalía y se bloqueó el acceso en 4 minutos
  • 22:35 UTC: se completó la restauración de todas las ramas

Proceso de infección

  • Cuando el desarrollador ejecutó pnpm install, se activó el script preinstall del paquete malicioso, que descargó y ejecutó TruffleHog
  • TruffleHog escaneó y exfiltró tokens de GitHub, credenciales de AWS, tokens de npm y variables de entorno
  • En la máquina infectada se encontraron el directorio .trufflehog-cache y archivos relacionados
  • El paquete que causó la infección fue eliminado y ya no puede rastrearse

Actividad del atacante

  • Tras la infección, mantuvo actividad de reconocimiento durante 17 horas
    • Clonó 669 repositorios usando infraestructura ubicada en Estados Unidos e India
    • Mantuvo el acceso con un token de GitHub mientras monitoreaba la actividad del desarrollador
    • Creó un repositorio llamado “Sha1-Hulud: The Second Coming”, que se cree fue usado para almacenar credenciales
  • Después, realizó acciones destructivas durante 10 minutos
    • Intentó hacer force-push a 199 ramas en 16 repositorios
    • Cerró 42 PR; algunas acciones fueron bloqueadas por la protección de ramas
    • Todos los commits aparecían con el formato “Linus Torvalds <email> / init”

Detección y respuesta

  • La anomalía se detectó en tiempo real mediante alertas de Slack
  • En 4 minutos se bloqueó el acceso a GitHub de la cuenta infectada, y luego se revocó también el acceso a todos los servicios, incluidos AWS, Vercel y Cloudflare
  • El análisis de logs de AWS CloudTrail mostró solo llamadas de API de solo lectura, sin acceso a datos de producción
  • AWS también detectó por su cuenta actividad sospechosa relacionada con Shai-Hulud y envió una alerta

Daño y recuperación

  • 669 repositorios clonados, 199 ramas con intentos de force-push y 42 PR cerrados
  • La recuperación fue difícil por la falta de reflog del lado del servidor en GitHub, pero usando la Event API y el reflog local se restauró todo en 7 horas
  • Los paquetes de npm y la infraestructura de producción no fueron comprometidos

Exposición de una clave de GitHub App

  • Durante la investigación se encontró una clave privada de GitHub App en la papelera de la laptop del desarrollador
  • Esa clave tenía permisos de lectura/escritura sobre repositorios de clientes y fue rotada de inmediato
  • Como la base de datos (donde se almacenan los installation IDs) no fue comprometida, no hay evidencia de acceso a repositorios de clientes, aunque no puede descartarse por completo
  • Se solicitó al soporte de GitHub acceso a logs adicionales y se envió un aviso por correo electrónico a los clientes

Análisis técnico de Shai-Hulud

  • Al ejecutarse setup_bun.js, instala el runtime de Bun y ejecuta bun_environment.js en segundo plano
  • Usa TruffleHog para recolectar credenciales dentro del directorio $HOME
  • Los datos recolectados (contents.json, cloud.json, truffleSecrets.json, etc.) se suben a repositorios aleatorios de GitHub en formato de triple codificación base64
  • Si existe un token de npm, modifica y vuelve a publicar paquetes de la cuenta infectada para propagar el gusano
  • Si no encuentra credenciales, intenta borrar el directorio home
  • Archivos indicadores de infección: setup_bun.js, bun_environment.js, .trufflehog-cache/, etc.

Medidas de refuerzo de seguridad

  • Desactivación total de scripts de npm (ignore-scripts=true)
  • Actualización a pnpm 10: scripts desactivados por defecto y configuración de minimumReleaseAge (3 días) para retrasar la instalación de paquetes nuevos
  • Adopción de npm Trusted Publishers basado en OIDC para eliminar tokens de larga duración
  • Protección de ramas aplicada a todos los repositorios
  • Adopción de Granted en AWS SSO, con cifrado de tokens de sesión
  • En GitHub Actions, ahora se requiere aprobación para ejecutar workflows de contribuidores externos

Lecciones para otros equipos

  • La propia ejecución arbitraria de código al instalar paquetes npm es una superficie de ataque
  • Es necesario configurar ignore-scripts=true y mantener una lista blanca solo de los paquetes necesarios
  • pnpm minimumReleaseAge ayuda a retrasar la instalación de paquetes nuevos
  • La protección de ramas y los despliegues basados en OIDC son medidas de seguridad esenciales
  • No se deben almacenar credenciales de larga duración en máquinas locales; solo se debe permitir el despliegue mediante CI
  • El ruido de las alertas de Slack fue la clave para la detección

Aspecto humano

  • El desarrollador infectado no tuvo la culpa; el incidente ocurrió simplemente por ejecutar npm install
  • Durante el ataque se encontraron rastros de que esa cuenta marcó automáticamente con “star” cientos de repositorios aleatorios
  • El incidente no revela un error individual, sino una vulnerabilidad estructural del ecosistema

Métricas resumidas

  • Desde la infección inicial hasta el primer ataque: unas 2 horas
  • Tiempo durante el cual el atacante mantuvo acceso: 17 horas
  • Duración de la actividad destructiva: 10 minutos
  • 5 minutos hasta la detección, 4 minutos hasta el bloqueo
  • 7 horas hasta completar toda la recuperación
  • Repositorios clonados: 669 / Ramas afectadas: 199 / PR cerrados: 42

Recursos de referencia

  • Socket.dev: Shai-Hulud Strikes Again V2
  • Informes de análisis de PostHog, Wiz, Endor Labs y HelixGuard
  • Documentación de npm Trusted Publishers, pnpm onlyBuiltDependencies, minimumReleaseAge y Granted

3 comentarios

 
click 2025-12-15

Se suponía que pnpm, por su estructura, debía permitir post-install de forma individual por defecto, pero al final parece que incluso los desarrolladores terminan autorizándolo inconscientemente.

 
lamanus 2025-12-16

Entiendo que npm está configurado para ejecutarse de forma predeterminada, así que cambiaron a pnpm y desactivaron ese comportamiento por defecto para reforzar la seguridad.

 
GN⁺ 2025-12-15
Comentarios de Hacker News
  • Ejecutar npm install no es negligencia
    El problema es un ecosistema que permite ejecutar código arbitrario durante la instalación de paquetes
    Pero la falla de seguridad de fondo es usar un gestor de paquetes donde un tercero puede meter código en mi producto sin ninguna verificación
    Al final dependemos indefinidamente de la buena fe y la capacidad de los gestores de paquetes y de quienes los operan
    Además, parece que el OP da a entender que guardó credenciales en texto plano en el sistema de archivos

    • Creo que ambas cosas son un problema
      A nivel de lenguaje se puede crear una estructura que limite que el código lea entradas, consuma recursos y solo genere salidas correctas en tipo
      No resuelve por completo el problema de la cadena de suministro, pero reduce muchísimo la superficie expuesta
    • Esta lógica es demasiado circular
      Es como decir: “no está mal que una persona use estas herramientas, pero si todos las usan, entonces el ecosistema es el problema”
      Ya se ha demostrado muchas veces que muchas herramientas de desarrollo son inseguras
      Si de verdad te importa, deberías demostrarlo con acciones
    • Lo mismo pasa con los plugins del IDE
      Cuando usaba VS Code, era molesto tener que instalar plugins hechos por quién sabe quién solo para agregar una función pequeña
    • Me intriga cómo se podría diseñar un gestor de paquetes que impida ejecutar código de terceros
      Al final da la impresión de que necesariamente terminas ejecutando código en el que tienes que confiar
    • Algunas herramientas solo soportan archivos netrc para autenticación por HTTP
      Si usas git sobre HTTP, casi siempre existe esta ruta de exposición de credenciales en texto plano
  • Hace un año, el maintainer de pnpm propuso “bloquear por defecto los scripts post-install
    Desde la perspectiva del usuario sería incómodo, pero creía que a largo plazo sería un cambio que todos agradecerían
    PR relacionada: pnpm/pnpm#8897

    • Y aun así el mismo problema se sigue repitiendo
      Al final este es otro caso en el que la conveniencia le ganó a la seguridad
  • Dijeron que “la base de datos no fue comprometida”, pero si el atacante tuvo acceso a AWS y a secretos, yo diría que ya fue comprometida
    Si existió la posibilidad de acceso, debería considerarse una intrusión

    • Si hay logs de acceso a recursos de AWS y no hay rastro de acceso antes de revocar permisos, entonces se puede considerar que los datos están a salvo
  • Una vez que el malware se ejecuta, rastrear el origen es casi imposible
    pnpm install también se completa normalmente, así que es difícil de detectar
    Si hubieran tenido un EDR como Sentinel One o CrowdStrike, probablemente habría habido más pistas para investigar

    • Si hubieran tenido EDR, es muy probable que hubieran detectado o bloqueado la cadena de ataque de Trufflehog
  • Me llamó la atención la parte de “clonaron un total de 669 repos”
    Me pregunto si es normal que una empresa con menos de 100 empleados tenga más de 600 repositorios

    • Es completamente normal. Los repos son ganado, no mascotas
    • Nuestra organización tiene 7 personas, pero hay 365 repos en GitHub
      En la cantidad de repos influye mucho más la antigüedad y el ciclo de vida de los proyectos que el tamaño del equipo
    • Eso es lo que pasa cuando tienes a un arquitecto fan de los microservicios
  • pnpm ya dejó de ejecutar automáticamente scripts de ciclo de vida como preinstall, así que parece que estaban usando una versión vieja
    Ver PR relacionada

    • Al final del artículo mencionan que actualizaron a la última versión mayor
    • Yo pensaba que esa era la razón principal para usar pnpm, así que me confunde
    • Si al final actualizaron dependencias con un gestor de paquetes viejo, entonces eso es responsabilidad de ellos
    • También podría ser que el propio proyecto tuviera un script postinstall
      pnpm bloquea los scripts de las dependencias, pero los scripts a nivel de proyecto siguen ejecutándose
  • Se agradece que compartieran un análisis post-mortem de forma transparente
    Casos así son importantes para toda la industria
    Me pregunto si el tráfico del ataque podía distinguirse del tráfico normal de desarrollo
    Nosotros también queremos reforzar el filtrado de egress en el entorno de desarrollo, pero npm install se rompe seguido, así que es complicado

    • Usar la lista de IP permitidas de las organizaciones de GitHub probablemente ayudaría a defenderse de este tipo de ataques
  • He estado pensando en la seguridad de git en una laptop personal
    Por ahora hago push con claves SSH guardadas localmente
    También tengo privilegios de administrador, así que es riesgoso. Me pregunto cómo hacerlo más seguro

    • Recomiendo usar 1Password para gestionar claves SSH y firmas de Git, y hacer push/pull con GitHub OAuth o inicio de sesión desde CLI
      Así puedes separar la clave de firma de la clave de acceso, y además manejar por separado la cuenta de administrador
      Documentación relacionada: 1Password SSH Agent, Git Commit Signing, GitHub OAuth, GitHub CLI Login
    • Yo guardo mi clave privada SSH en el TPM y la uso desde el agente SSH vía PKCS11
      La ventaja es que la clave no puede filtrarse hacia afuera, pero si un malware corre en mi máquina, sigue siendo un riesgo
      Ejemplo en Linux, ejemplo en macOS
    • Si la laptop se infecta, no hay defensa posible
      Con TPM o Yubikey puedes hacerlo un poco más difícil, pero una defensa completa es imposible
      Lo seguro es hacer las tareas administrativas en una máquina separada y dedicada
    • También existe la opción de poner una clave GPG en un Yubikey y usar autenticación SSH con gpg-agent
      Al hacer push o commit, se desbloquea ingresando el PIN
    • Si bloqueas el push directo a la rama main y haces obligatorio el MFA, al atacante le costará mucho más acceder de inmediato a la rama de despliegue
  • Los commits con el nombre Torvalds eran una marca distintiva común después de la infección
    También se menciona en el análisis oficial de Microsoft
    Este gusano era muy ruidoso, y algunos atacantes usaron credenciales expuestas para hacer públicos repos privados o modificar readmes como forma de autopromoción

  • Me pregunto si este tipo de detección habría sido posible si el atacante hubiera exfiltrado información en silencio sin hacer nada destructivo