El 12 de octubre GitLab publicó tres versiones a la vez —19.4.2, 19.3.5 y 19.2.8— por CVE-2026-97229, un desbordamiento de entero en el compilador de expresiones regulares de CI/CD puntuado con un CVSS 10.0. La nota de la publicación lo describe como un fallo que «could have allowed an unauthenticated user to execute arbitrary code…». GitLab.com ya corría la versión parcheada y los clientes de GitLab Dedicated no tienen que hacer nada; el servidor que tienes en la planta de abajo, sí.
En la misma página, bajo «Important notes on upgrading», hay esta línea: «This patch will cause downtime during the upgrade as migrations must complete before GitLab can start». El epígrafe que la encabeza es «Single-node instances», que es como está montado casi todo el GitLab autogestionado que nos encontramos en empresas pequeñas y medianas. Aplicar el parche crítico significa, ahí, parar el servicio hasta que terminen las migraciones de base de datos. La 19.3.5 añade, además, migraciones que se ejecutan después del despliegue.
La pregunta que llega después del reinicio
Si tu instancia estaba en una versión afectada y permitía que entrara gente de fuera, parchear no cierra el asunto: alguien tiene que decidir si se da por comprometida. Y si se da, el trabajo deja de ser una actualización y pasa a ser una reconstrucción con rotación de credenciales.
En agosto ya contamos qué se lleva de verdad una copia de GitLab y por qué el fichero de secretos se queda fuera —clonar el repositorio no es tener copia de GitLab—. El resumen: gitlab-backup create no guarda /etc/gitlab, y la razón que da la documentación es buena, «Storing encrypted information in the same location as its key defeats the purpose of using encryption in the first place».
Lo que no contamos entonces es qué pasa el día que levantas esa copia en otra máquina y el fichero no aparece, o aparece pero es de otra instalación. Hay procedimiento documentado para eso. Empieza borrando.
El aviso que encabeza el procedimiento
La guía de resolución de problemas de GitLab reparte tres avisos en dos bloques antes de cualquier comando: «The steps in this section can potentially lead to data loss on the previously listed items», «Consider opening a Support Request if you're a Premium or Ultimate customer» y, bajo un encabezado propio, «Be sure to create a full database backup before attempting any changes». Hay que repetirlos, porque lo que viene después se ejecuta contra la base de datos a mano y no tiene deshacer.
Para las variables de CI/CD, el procedimiento es exactamente esto:
DELETE FROM ci_group_variables;
DELETE FROM ci_variables;
Y para los tokens de los runners, cinco sentencias sobre cuatro tablas, con los comentarios tal como vienen en la documentación:
-- Clear project tokens
UPDATE projects SET runners_token = null, runners_token_encrypted = null;
-- Clear group tokens
UPDATE namespaces SET runners_token = null, runners_token_encrypted = null;
-- Clear instance tokens
UPDATE application_settings SET runners_registration_token_encrypted = null;
-- Clear key used for JWT authentication
-- This may break the $CI_JWT_TOKEN job variable:
-- https://gitlab.com/gitlab-org/gitlab/-/issues/325965
UPDATE application_settings SET encrypted_ci_jwt_signing_key = null;
-- Clear runner tokens
UPDATE ci_runners SET token_encrypted = null;
El aviso que acompaña a ese bloque es el que conviene leer dos veces: «The final UPDATE operation stops the runners from being able to pick up new jobs. You must register new runners».
Falta la parte de integraciones y webhooks, que se resuelve con un TRUNCATE sobre ocho tablas en cascada, y la del segundo factor, que la documentación despacha desactivándolo para todos los usuarios para que cada uno lo vuelva a dar de alta. Llamar a esto restaurar sería generoso: lo que se hace es vaciar lo que ya nadie puede leer para que el servicio arranque, que es lo único que se puede hacer con un valor cifrado cuya clave no existe.
Nueve cosas inaccesibles, y ninguna es el código
La misma guía enumera qué queda inaccesible cuando la clave se pierde. Son nueve: variables de CI/CD, integración con Kubernetes y GCP, dominios personalizados de Pages, seguimiento de errores de proyecto, autenticación de los runners, réplica de proyectos, integraciones, webhooks y tokens de despliegue.
La réplica de proyectos es la que se rompe sin ruido: el espejo deja de replicar y, si tu copia secundaria de código dependía de él, se queda congelada en la fecha del incidente sin que salte ninguna alarma.
En la lista no sale el código. Los repositorios están en la copia y vuelven enteros, y por eso este agujero aguanta años sin que nadie lo vea: la prueba de restauración termina cuando aparece el git log de siempre y todo el mundo respira. La maquinaria que empuja ese código hasta producción no entra en esa prueba.
Lo que cuesta volver a escribir lo borrado
El DELETE tarda un segundo. La factura llega después, cuando hay que volver a poner cada variable en su sitio, y para eso hace falta saber qué valor tenía. Si la contraseña del registro de contenedores, la clave del proveedor de nube o el token del sistema de tickets vivían únicamente en ese campo cifrado, la única copia de esa credencial estaba dentro de la aplicación que acabas de vaciar.
En nuestra experiencia, ahí está la conversación que cuesta: el gestor de contraseñas de la empresa y el panel de variables del CI llevan años divergiendo sin que nadie los compare.
La letra pequeña de la precondición
La nota de GitLab habla de un usuario no autenticado; el análisis de Rapid7, recogido por Help Net Security, añade la condición completa: el atacante tiene que poder subir a una rama de un proyecto público «a malicious CI/CD configuration file .gitlab-ci.yml, and a repository path containing UTF-8 characters». Los investigadores reprodujeron la ejecución de código sobre la imagen oficial de GitLab CE 19.4.1 y la misma entrada no funcionó contra la 19.4.2.
La diferencia no cambia si hay que parchear —hay que parchear, y Rapid7 recomienda hacerlo con urgencia, fuera de los ciclos de parcheo habituales—, pero sí cambia cuántas horas tienes y qué hay que mirar primero. Si en tu instancia un usuario cualquiera puede crear un proyecto público y empujar una rama, la precondición se cumple rellenando un formulario de alta. Si está detrás de la VPN y solo entra gente con nómina, tienes más margen, cosa distinta de estar a salvo. En el momento del análisis de Rapid7 no había explotación confirmada en la vida real.
La comprobación que no es rápida
Hay una tarea que responde, sobre la instancia viva, si todo lo cifrado se puede leer todavía:
sudo gitlab-rake gitlab:doctor:secrets VERBOSE=1
Recorre todos los valores cifrados de la base de datos y comprueba que se descifran con el fichero de secretos actual. Resume con una línea del tipo Total: 1 row(s) affected, y con VERBOSE=1 dice además qué filas y columnas fallan, con nombres como Group[10].runners_token. La propia documentación avisa de que «This can take a very long time, depending on the size of your database, as it checks all rows in all tables», así que no es cosa de lanzarla a las once de la mañana de un martes sin mirar la carga.
Si ese número no es cero, ya hay valores que nadie puede leer, hoy, con la instancia funcionando y el panel en verde.
Lo que das por perdido, decidido antes
La parte que casi nunca está escrita es la decisión previa: qué piensas recuperar, qué piensas rehacer y, sobre todo, dónde vive la copia maestra de cada cosa. Se toma a plena luz y sin prisa, y ocupa cinco líneas en media hoja:
- Variables de CI/CD. ¿La copia maestra está en el gestor de contraseñas o solo en GitLab? Si la respuesta honesta es «habría que ir preguntando», lo que falta es un inventario de credenciales.
- Runners. Volver a registrarlos sale barato siempre que alguien sepa cuántos hay y en qué máquinas. Esa lista, ¿existe fuera de la propia instancia que acabas de perder?
- Tokens de despliegue. Quién los consume al otro lado: un servidor, un clúster, el proveedor de nube. Anularlos sin esa lista deja servicios parados sin que nadie sepa por qué.
- Segundo factor. Desactivarlo para todos es una línea en la documentación y una mañana de trabajo de una persona avisando y verificando identidades.
- Réplica, integraciones y webhooks. Apunta hoy cuáles hay, porque son lo último que alguien echa en falta y lo primero que rompe un flujo de trabajo en silencio.
Restaurar y responder piden cosas distintas
Si la conclusión del análisis es que el servidor estuvo comprometido, todo lo que ese servidor podía descifrar hay que darlo por quemado: variables, tokens, claves de despliegue. Para lo que sirve entonces el fichero de secretos es para leer qué había y poder rotarlo allí donde se consume, que es un trabajo distinto y bastante más largo.
Las dos tareas tiran en direcciones contrarias: restaurar quiere la clave viva y la respuesta al incidente la quiere muerta. Por eso el orden se decide antes, en frío, igual que cuando no hay parche disponible y toca gobernar el riesgo con lo que hay —tu política de parcheo no contempla que no haya parche—. Aquí parche hay, y aun así la mitad del trabajo se queda fuera de lo que el parche resuelve.
Lo que queda apuntado
El parche de este lunes es urgente y se aplica con una parada corta. Lo que no cabe en esa ventana es averiguar si, el día que haga falta levantar la copia en otra máquina, lo que vuelve es un GitLab funcionando o un GitLab con el historial intacto y las credenciales vacías. La tarea de comprobación tarda lo que tarde, pero eso lo responde hoy.
Fuentes (consultadas el 14-10-2026): las versiones, la fecha, el CVSS 10.0, la descripción de CVE-2026-97229, el aviso de parada durante la actualización y la situación de GitLab.com y Dedicated salen de la nota del parche crítico 19.4.2, 19.3.5 y 19.2.8. La precondición completa, la reproducción sobre la imagen 19.4.1, la ausencia de explotación confirmada y la recomendación de actualizar con urgencia fuera de los ciclos de parcheo habituales son del análisis de Rapid7 recogido en Help Net Security. Los avisos previos, las sentencias SQL y la lista de las nueve cosas inaccesibles, de la guía de resolución de problemas de copias; el motivo por el que la configuración no va dentro de la copia, de la documentación de copias de seguridad; y la tarea gitlab:doctor:secrets, su modo VERBOSE y el aviso sobre su duración, de la página de tareas de comprobación. No hemos reproducido la vulnerabilidad: aquí no hay análisis propio del fallo, solo lectura de fuentes públicas y la parte operativa. Son nuestros la lectura de que lo que no vuelve es la maquinaria y no el código, el orden de la decisión previa, el guion de cinco líneas y la observación sobre cómo está montado el GitLab autogestionado que nos encontramos.
Levantar la copia en otra máquina, antes de necesitarlo
En everyWAN montamos y ensayamos planes de recuperación ante desastres en los que lo que se mide es el servicio levantado y en funcionamiento; revisamos qué queda expuesto y qué hay que rotar desde ciberseguridad; y mantenemos al día el stack de datos y aplicaciones sobre el que trabaja un equipo de desarrollo.
Hablar con everyWAN