Repositorio de Zod comprometido: comprueba la publicación en npm
El repositorio de Zod contenía código malicioso, pero no llegó a npm ningún paquete de Zod infectado. Cuatro comprobaciones delimitan la exposición.
Ernest Bursa
No se publicó en npm ningún paquete malicioso de Zod 4.6.6. El 21 de septiembre de 2026 llegaron cambios maliciosos al repositorio de Zod en GitHub, pero esos cambios no activaron su proceso de publicación. Standard Schema tampoco llegó a la fase de publicación. Una versión propuesta de @nubjs/[email protected] entró en el proceso de validación de npm y quedó bloqueada antes de estar disponible para instalarla. Son tres resultados distintos de un mismo incidente, según el informe de npm y GitHub y la aclaración de Colin McDonnell, responsable de Zod.
Si comprometen tu repositorio, la pregunta urgente no es solo si apareció código malicioso en GitHub. Comprueba qué barrera llegó a cruzar: el historial del código, la autorización para publicar, la validación del registro y el contenido del paquete; después, investiga quién pudo obtenerlo. Cada paso exige pruebas y una respuesta propias.
¿Qué ocurrió con Zod, Standard Schema y Nub?
El 21 de septiembre aparecieron cambios maliciosos relacionados entre sí en los repositorios públicos de Zod, Standard Schema y Nub. El equipo que respondió al incidente enlazó los cambios afectados y describió código diseñado para descargar y ejecutar más JavaScript durante la instalación si llegaba a un paquete y alguien lo instalaba. La información publicada no demuestra que ese código llegara a ejecutarse en el equipo de ningún usuario. Los metadatos de los cambios tampoco permiten saber cómo obtuvo acceso el atacante.
El resultado de cada publicación dice más que un titular sobre los tres casos:
| Proyecto | Hasta dónde llegó el cambio malicioso | Resultado de la publicación en npm |
|---|---|---|
zod |
Cambio en el repositorio público | No entró en el proceso de publicación de npm; ninguna versión maliciosa de Zod quedó disponible para instalar |
standard-schema |
Cambio en el repositorio público | No entró en el proceso de publicación de npm; ninguna versión maliciosa de Standard Schema quedó disponible para instalar |
@nubjs/[email protected] |
Proceso de publicación de npm | La validación retuvo la versión y la bloqueó antes de que estuviera disponible públicamente |
La tabla se basa en los registros de publicación del equipo de respuesta, datos que no se pueden deducir de la página de un cambio en GitHub. También aclara un equívoco habitual: Nub llegó al proceso interno de npm, no a una versión pública instalable. Que se enviara una versión no significa que un usuario pudiera descargarla de la forma habitual.
El equipo de respuesta describió juntos los tres repositorios, pero McDonnell objetó que cierta redacción podía dar a entender que el escáner de npm había detenido a Zod. Según su testimonio directo en esa misma conversación, los cambios maliciosos no activaron el proceso de publicación de Zod. Standard Schema tampoco publicó nada. El escáner de npm detectó el intento de publicar Nub. El detalle del informe y la corrección del responsable coinciden en los resultados; lo que cuestiona McDonnell es la impresión que puede dejar el planteamiento general.
¿Por qué la intrusión en el repositorio de Zod no llegó a npm?
Enviar un cambio a Git modifica el repositorio. Por sí solo, no autoriza una publicación en npm. McDonnell explica que Zod utilizaba un entorno específico de despliegue de GitHub y la publicación de confianza de npm en su CI de publicación; un envío normal de cambios o etiquetas no podía activar ese proceso. Planteó la posibilidad de que se hubiera comprometido una credencial para enviar cambios a Git, pero indicó que aún se investigaba cómo se había producido la intrusión. Es una hipótesis, no una conclusión. Su relato sí establece el hecho necesario aquí: los cambios maliciosos no ejecutaron el proceso de publicación de Zod.
La publicación de confianza utiliza una identidad de CI configurada y OIDC para autorizar la publicación sin entregar al proceso un token permanente de npm. Responde a quién o qué puede solicitar una publicación, no a si el código resultante es inocuo. Los entornos de GitHub pueden restringir ramas y exigir aprobaciones antes de que se ejecute una tarea o reciba secretos del entorno. En tu repositorio, la protección real depende de los desencadenantes, los permisos, las reglas del entorno y las posibles vías alternativas de publicación. Si se compromete el proceso de CI o existe otro publicador autorizado, el análisis cambia.
Nub muestra la siguiente barrera. Según McDonnell, su CI de publicación sí podía activarse al crear una etiqueta. El paquete 0.9.4 propuesto llegó a npm, donde la validación previa a la publicación lo señaló y lo retuvo. La descripción del análisis de npm distingue entre enviar un paquete y ponerlo a disposición de los usuarios. El análisis detuvo este intento de publicar Nub. No fue el control que detuvo a Zod ni a Standard Schema: ninguno de los dos entró en ese proceso.
Ninguna de estas barreras descarta que alguien utilizara una dependencia obtenida directamente de Git, descargara el código fuente o ejecutara código de una versión antigua y comprometida del repositorio. El informe del incidente no confirma ejecución posterior por esas vías ni aporta un recuento verificado de instalaciones afectadas.
¿Por qué alarmaban las notas de la versión 4.6.6 de Zod?
El 12 de octubre, una conversación en Hacker News sobre una posible infección de Zod 4.6.6 señaló un cambio malicioso de septiembre que figuraba en la lista automática de cambios de la página de la versión v4.6.6 en GitHub. Era una pista que merecía investigarse, pero no demostraba que el código estuviera dentro del paquete distribuido por npm.
El cambio malicioso se había revertido, aunque seguía figurando en el intervalo de cambios utilizado para generar las notas de la versión. McDonnell aclaró que el código no estaba en el paquete publicado. Ahí es fácil confundir una pregunta sobre el historial del repositorio con una conclusión sobre el contenido del paquete. Una etiqueta puede abarcar la introducción del código y su reversión. Comprueba qué produjo la compilación y qué puso a disposición el registro antes de afirmar que una instalación estaba infectada.
Hubo además otro cambio el 12 de octubre, distinto de la respuesta del 21 de septiembre. McDonnell dijo que eliminó los cambios maliciosos del historial tras sopesar la transparencia frente al riesgo de que una extracción de una versión antigua, por ejemplo con git bisect, pudiera ejecutarlos. También indicó que esa limpieza hizo que v4.6.6 perdiera la información de procedencia de npm, y después se publicó v4.6.7. Su actualización describe una consecuencia posterior de reescribir el historial. Esa pérdida de procedencia no demuestra que el paquete 4.6.6 anterior contuviera malware.
La información de procedencia tiene una función concreta. La documentación de npm describe declaraciones que vinculan un paquete publicado con su repositorio de origen y el proceso que lo generó. Ese vínculo ayuda a investigar cómo se produjo el paquete. No es un análisis de malware, y un resumen automático de la versión en GitHub no sustituye a la inspección del paquete.
¿Qué cuatro barreras debe comprobar quien mantiene un paquete?
Utiliza estas cuatro comprobaciones ante cualquier sospecha de intrusión que pueda afectar a un paquete. Empieza por lo observado, conserva las pruebas y avanza solo cuando tengas datos propios sobre la siguiente barrera. Es un método práctico derivado del caso Zod y de los controles de publicación de npm y GitHub. No significa que todos los pasos siguientes se dieran en el incidente de septiembre.
1. Determina qué cambió en el repositorio
Anota los identificadores de los cambios sospechosos, los archivos afectados, las horas de autoría y de envío, las ramas y etiquetas, y cuándo se retiró el código. Conserva el diff original y los registros antes de limpiar el historial. Examina por separado los manifiestos del paquete, los scripts que se ejecutan durante su instalación, los scripts de compilación y los archivos de CI. Un cambio inactivo en el repositorio puede activarse al compilar o instalar; un cambio alarmante también puede haberse revertido antes de cualquier compilación.
Redacta una frase que separe observación de explicación: «El cambio X añadió código al archivo Y y se envió a la hora Z» describe un hecho observado. «La causa fue una clave SSH robada» exige pruebas sobre credenciales o accesos. El relato publicado por McDonnell no establece una vía de entrada definitiva. No la inventes en tu comunicación sobre el incidente. El registro del caso enlaza los cambios públicos sin demostrar cómo se obtuvo la credencial inicial.
Esta barrera indica dónde existió código malicioso, incluidas las versiones antiguas del historial, pero no si se ejecutó un proceso de publicación. Conserva las pruebas si después reescribes el historial público.
2. Comprueba si el proceso de publicación podía ejecutarse y si se ejecutó
Lee la configuración del proceso en lugar de deducirlo de una etiqueta. ¿Qué evento activa el proceso: un envío de cambios, una etiqueta o una ejecución manual? Una vez activado, ¿debe aprobarse el entorno antes de que se ejecute la tarea? ¿Qué ramas o etiquetas admite? ¿Quién puede editar las reglas del proceso y del entorno? ¿Acepta npm solo la identidad OIDC prevista o siguen autorizados un token de publicación directa y otro proceso?
Después, examina el registro de ejecuciones del periodo sospechoso. Conserva el identificador de la ejecución, la versión del archivo de configuración, la referencia que la activó, el actor, la aprobación del entorno y el resultado del intento de publicación. «El proceso se activa manualmente» y «no hubo ninguna ejecución relevante» son afirmaciones distintas. Una captura de la configuración posterior a la reparación no demuestra qué reglas estaban vigentes durante la intrusión; conserva la configuración histórica y los registros de auditoría correspondientes.
En este caso, McDonnell afirma que el proceso de publicación de Zod no se ejecutó para los cambios maliciosos. Es una prueba más concreta que suponer que npm los habría detectado. La publicación de confianza y los entornos de despliegue explican los controles generales; su declaración aporta el resultado específico de Zod.
3. Comprueba el estado en el registro e inspecciona el paquete
Separa cuatro estados: se intentó publicar; una versión entró en validación; quedó retenida o bloqueada; o llegó a estar disponible públicamente para instalarla. Para cualquier versión pública, registra el nombre y la versión exactos, la hora de publicación, la URL y el resumen criptográfico del archivo distribuido, y los archivos que contiene. Compara ese paquete con el código sospechoso y con el resultado de la compilación. Un número de versión de npm no indica por sí solo qué datos recibieron los usuarios.
El equipo del registro puede tener registros privados de validación. Indica en qué pruebas se apoya cada conclusión y quién las aportó. El informe del incidente señala que Nub entró en validación y quedó bloqueado antes de estar disponible, mientras que los cambios maliciosos de Zod y Standard Schema no dieron lugar a intentos de publicación. La página de una versión no bastaba para determinar esos resultados.
Si el paquete tiene información de procedencia, úsala para vincularlo con el código fuente y el proceso que lo generó. Si falta, dilo e investiga por qué. La ausencia de ese distintivo no es un veredicto de malware, ni su presencia prueba que el código sea seguro. La guía de procedencia de npm explica qué vínculo acredita.
4. Determina quién pudo obtener el código
Una vez sepas si llegó a estar disponible un paquete malicioso, busca pruebas en los sistemas que lo consumen. Comprueba archivos de dependencias fijadas, registros de instalación, cachés, manifiestos de despliegue y registros de compilación para encontrar la versión y el resumen criptográfico exactos. Revisa por separado las URL de Git, las descargas directas del código fuente, los paquetes locales y las versiones antiguas extraídas del historial. Un bloqueo en el registro reduce la exposición a través de ese registro, pero no aclara si alguien obtuvo el código por otra vía.
Distingue disponibilidad, descarga y ejecución. Un paquete público puede haber estado disponible sin aparecer en tus compilaciones. Un archivo de dependencias puede incluir una versión sin demostrar que se ejecutara un script de instalación en un entorno determinado. Alguien pudo descargar el código fuente sin ejecutarlo. Cuando las pruebas solo alcanzan una de esas etapas, limita también a ella tu comunicación.
Conserva los registros antes de que la rotación de tokens, el borrado de cachés o la limpieza del repositorio eliminen el periodo que necesitas investigar. Rota o revoca las credenciales con rapidez cuando proceda, pero verifica que las antiguas ya no funcionen. Nuestra lista de comprobación para cerrar un informe sobre credenciales filtradas distingue entre retirar la exposición, revocar la credencial e investigar lo ocurrido. No sustituye estas comprobaciones de publicación.
¿Cómo debes comunicar el resultado y mejorar la publicación?
Publica un resumen breve que distinga cada barrera. Indica qué repositorios cambiaron, qué procesos se ejecutaron o no, qué versiones entraron en validación y qué paquetes llegaron a poder instalarse. Señala la procedencia de cada prueba y las preguntas aún abiertas. Quien mantiene un paquete puede decir «encontramos código malicioso en el historial, pero no se publicó ningún paquete malicioso en npm» sin restar importancia a la intrusión. Si hubo un paquete público afectado, hay que nombrar las versiones exactas y a los usuarios expuestos, no limitarse a decir «comprometieron el repositorio».
Separa las fechas. El 21 de septiembre se produjeron los cambios maliciosos en los repositorios y el envío de Nub que acabó bloqueado. En una edición del 24 de septiembre, McDonnell indicó que sus paquetes habían pasado a la publicación por etapas, limitada por entornos y activada manualmente. Fue una mejora posterior, no el control que ya había detenido a Zod. Según la documentación de npm, enviar una versión a esa fase no requiere por sí mismo autenticación de dos factores; sí la requiere aprobar su publicación. Una configuración que solo permita publicar por etapas puede rechazar la publicación directa, pero la guía de configuración de npm también permite varios publicadores. Comprueba que no quede abierta ninguna vía directa imprevista.
Cuando un hallazgo afecta a varios proyectos, coordina la comunicación externa sin confundir sus resultados. Una intrusión limitada al repositorio, un intento de publicación bloqueado y un paquete malicioso público requieren destinatarios y mensajes diferentes. Nuestra guía de divulgación coordinada entre proveedores trata por separado el reparto de responsabilidades y los plazos.
Kit puede recibir el informe de un investigador a través de un programa público de divulgación de vulnerabilidades, asignarle un responsable, seguir el acuse de recibo y la reparación, y conservar el historial de comunicación. Su flujo de CSIRT permite enlazar una corrección o una incidencia externa. El equipo de ingeniería debe seguir examinando por su cuenta el historial de Git, la autorización del proceso de publicación, el estado en npm y el contenido del paquete. Kit no analiza paquetes de npm ni certifica que una versión esté libre de malware.
El caso Zod deja una lección concreta y aplicable a otros incidentes: antes de estimar el alcance, determina qué barrera se cruzó. Reúne en una página las pruebas del repositorio, el proceso de publicación, el registro y el paquete. Después, explica a los usuarios qué quedó expuesto, qué se detuvo y qué sigue sin saberse. Si necesitas un canal claro para el próximo aviso, empieza por la guía para crear un programa de divulgación.
Artículos relacionados
Prueba Kit durante 30 días.
Contratación, informes de seguridad y formación en una sola cuenta, para equipos donde nada de eso es un trabajo a tiempo completo. 30 días gratis, con tarjeta. Cancela antes de que termine y no pagas nada.
Empieza gratis