«The attack came close to working» (el ataque estuvo cerca de funcionar). Así resume Alpine Linux, en el comunicado que publicó el 28 de octubre de 2026, los ocho intentos de explotar CVE-2026-97229 que un atacante lanzó los días 20 y 21 contra gitlab.alpinelinux.org, el servidor desde el que la distribución gestiona el código de sus paquetes. El proyecto no ha encontrado pruebas de que el exploit ejecutara código, pero tampoco puede descartarlo, así que ha reconstruido la instancia desde cero, ha reiniciado todas las contraseñas y ha sustituido las claves secretas de GitLab.
- Un atacante intentó explotar CVE-2026-97229 en gitlab.alpinelinux.org los días 20 y 21 de octubre de 2026, provocó ocho caídas del worker de GitLab y volvió sin éxito los días 25 y 26.
- Afecta a los usuarios de gitlab.alpinelinux.org, con todas las contraseñas reiniciadas, a quien use la imagen alpinelinux/gitlab descargada antes del 27 de octubre y a cualquier GitLab construido con un Ruby propio.
- GitLab corrige el fallo desde el 12 de octubre solo en sus paquetes e imágenes oficiales. Con un Ruby propio hace falta Ruby 3.3.13, 3.4.12 o 4.0.9, publicados el 23 de octubre.
- Alpine ha verificado que sus paquetes y las releases del 21 de octubre no fueron manipulados y que las claves de firma nunca estuvieron en el servidor.
- Lo urgente es comprobar qué Ruby ejecuta de verdad el proceso de GitLab y buscar errores 502 en ci/lint sin entrada en api_json.log desde el 12 de octubre.
Es la tercera pieza del blog sobre este fallo del motor de expresiones regulares Onigmo. Contamos el parche de GitLab del 12 de octubre y, nueve días después, el segundo fallo CVSS 10 en Onigmo, y en ambas ocasiones ninguna fuente había confirmado un intento real contra una instancia concreta. Alpine es la primera organización que describe uno con fechas, cifras y consecuencias, y lo hizo primero en su cuenta de Fosstodon, a las 21:27 UTC del 28 de octubre, con el resumen de lo ocurrido y la aclaración de que ni los paquetes ni las claves de firma se vieron afectados.
We have published a report on a security incident on gitlab.alpinelinux.org. An attacker tried to exploit CVE-2026-97229. We found no evidence it succeeded but can't rule it out, so we rebuilt
GitLab from scratch.Alpine packages and releases were not affected, and no signing keys were exposed.
All GitLab passwords have been reset. If you reused yours elsewhere, change it there.
https://alpinelinux.org/posts/GitLab-security-incident-CVE-2026-97229.html
Lo que Alpine Linux ha comprobado y lo que no puede descartar
El comunicado de Alpine separa con cuidado lo verificado de lo que ya no se puede verificar. En el primer grupo está lo que más preocupa a quien construye contenedores sobre esta distribución. Las versiones estables del 21 de octubre (3.21.9, 3.22.7, 3.23.7 y 3.24.3), sus imágenes ISO y los tarballs minirootfs de los que salen las imágenes oficiales de Docker se compilaron a partir de commits de aports cotejados con registros fuera del alcance del atacante, el clon de un desarrollador y los 45 servidores de compilación del proyecto. Lo mismo vale para todos los paquetes compilados entre el 20 y el 24 de octubre en todas las arquitecturas. Esas comprobaciones se hicieron el 24 de octubre y el proyecto las está extendiendo hasta el 27, el día en que cerró la instancia, porque el servidor siguió vulnerable todo ese tiempo.
Alpine añade que «No signing keys were exposed» (no se expuso ninguna clave de firma), porque las claves con las que firma paquetes y releases nunca han estado en el servidor de GitLab. Las sondas de tiempo del atacante no mostraron retardos, no aparecieron ficheros, claves, hooks ni procesos inesperados, y ninguna petición web o de API devolvió datos privados en los registros que el proyecto conserva.
El segundo grupo explica la decisión de rehacerlo todo. Un exploit que funciona no provoca caída ni deja rastro en los registros web. Parte de los registros del día del ataque ya no existen, en concreto los de los trabajos en segundo plano y el sistema de ficheros del contenedor antiguo. Y no había registro de conexiones salientes, ni en el servidor ni en el proveedor de alojamiento. Con ese hueco, Alpine enumera lo que un exploit con éxito habría podido leer, y la lista describe bien lo que guarda cualquier GitLab corporativo.
- Direcciones de correo, nombres y direcciones IP de todos los usuarios.
- Hashes de contraseñas y secretos de doble factor.
- Variables de CI/CD y tokens de los runners.
- Credenciales de webhooks, integraciones y espejos de push.
- Tokens de acceso personal, almacenados como hash.
- Repositorios privados e issues y merge requests confidenciales.
Por eso todas las contraseñas de gitlab.alpinelinux.org han quedado invalidadas. Cuando la instancia vuelva a abrirse, cada usuario tendrá que fijar una nueva desde el enlace de contraseña olvidada, y el proyecto pide cambiarla también en cualquier otro servicio donde se reutilizara, porque ha visto intentos automatizados de inicio de sesión con contraseñas filtradas de otros sitios. El comunicado completo, con su tabla de hitos, es el documento que conviene leer antes de tocar una instancia propia.

Ocho caídas del worker y una imagen descargada para afinar el exploit
La secuencia que cuenta Alpine empieza el 20 de octubre con una cuenta recién registrada que sondea el servidor. El 21 llega el ataque principal, con configuraciones de CI manipuladas que hacen caer los procesos worker de GitLab ocho veces, errores 502 para el resto de usuarios y trabajos de CI fallidos. En cuatro de esas caídas el atacante consiguió avanzar parte del exploit y falló en el paso siguiente. Entre medias descargó la imagen de contenedor alpinelinux/gitlab que usa el proyecto, un movimiento que Alpine interpreta como un intento de ajustar el exploit a su build exacto. La cuenta quedó bloqueada el 23, y los días 25 y 26 el mismo actor volvió a intentarlo sin resultado.
Esa lectura encaja con lo que Rapid7 documentó el 16 de octubre en su análisis técnico del fallo, firmado por Jonah Burgess. El diagrama de Rapid7 resume la cadena en seis pasos. El atacante sube a un proyecto público una configuración de CI con una expresión regular y una ruta de repositorio con caracteres UTF-8. La expresión se compila primero en US-ASCII y pasa las comprobaciones de GitLab. Al evaluarla contra la ruta UTF-8, Ruby la recompila sin volver a comprobar su tamaño, un desbordamiento con signo hace que Onigmo emita un programa mayor de lo que cabe en un entero de 32 bits, un salto envuelto lleva la ejecución a datos controlados por el atacante y la vuelta de la rutina acaba en una llamada a system con la cuenta git. La petición que lo dispara es un GET sin autenticar al endpoint de lint de CI con el parámetro content_ref, sin pipeline ni runner por medio.

La segunda figura de Rapid7 baja al detalle de la máquina virtual de Onigmo, con el bitmap de clase de caracteres controlado, la pila de backtracking redirigida a la ruta del repositorio y la dirección de retorno nativa sustituida. Es el tramo que depende de desplazamientos concretos de cada build, y por eso el README de la PoC que Rapid7 publicó el 13 de octubre advierte de que solo se probó contra la imagen oficial gitlab/gitlab-ce:19.4.1 para amd64 y de que otros builds necesitan ajustes. La imagen de Alpine lleva su propio Ruby, así que descargarla era el paso lógico para quien quisiera adaptar esos desplazamientos. El mismo README añade que la instancia de pruebas necesitó unos 9 GiB de memoria libre, y en un servidor con menos margen cada intento fallido se traduce en un worker que muere o se cuelga.

GitLab describe esas dos formas de caída en la nota de detección que publicó el 20 de octubre y revisó el 28. Si el salto envuelto mete a la máquina virtual en un bucle, el worker se queda clavado con unos 2,1 GiB adicionales de memoria residente hasta que el límite de rack-timeout corta la petición a los 59 segundos y Workhorse devuelve un 502. Si la compilación reserva más de 2 GiB de golpe en un servidor con poca memoria libre, el OOM killer del kernel lo mata en dos o tres segundos, y el 502 llega sin volcado de Ruby ni mensaje del proceso maestro. En los dos casos la petición que mató al worker nunca llega a escribirse en api_json.log, y esa ausencia es, según GitLab, la señal de mayor confianza de todo el conjunto de reglas. Las caídas y los 502 que Alpine registró el 21 de octubre son exactamente ese cuadro.
Por qué GitLab 19.3.6 no cerró CVE-2026-97229 en el contenedor de Alpine
El incidente tiene dos errores encadenados, y Alpine reconoce los dos. El primero es de fecha. GitLab publicó el 12 de octubre las versiones 19.2.8, 19.3.5 y 19.4.2 con un aviso que describía un fallo que «could have allowed an unauthenticated user to execute arbitrary code on the GitLab server» (podría haber permitido a un usuario sin autenticar ejecutar código arbitrario en el servidor de GitLab). Alpine actualizó el 13 de octubre a la 19.3.4 en lugar de a la 19.3.5, y esa versión seguía dentro del rango vulnerable. El listado de etiquetas de alpinelinux/gitlab en Docker Hub lo confirma: la 19.3.4 se publicó el 13 de octubre a las 11:25 UTC y nunca hubo una etiqueta 19.3.5.

El segundo error es el que afecta a más gente. El 22 de octubre, con el ataque ya en marcha, Alpine actualizó a la 19.3.6, la versión que GitLab había publicado el día anterior para cerrar el fallo del renderizador de ficheros .org. No sirvió. La corrección de CVE-2026-97229 vive fuera del código de GitLab, en un parche al intérprete de Ruby que GitLab retroportó a las ramas 3.3 y 3.4 de sus paquetes Omnibus y de sus imágenes CNG. La imagen de Alpine instala el Ruby estándar de la distribución, así que recibió el código nuevo de GitLab y conservó el Onigmo viejo. Ruby solo publicó el arreglo el 23 de octubre, en las versiones 3.3.13, 3.4.12 y 4.0.9, con una nota que acota el riesgo a las aplicaciones que compilan expresiones regulares a partir de cadenas controladas por el atacante y avisa de que «Regexp.timeout does not protect against these bugs» (Regexp.timeout no protege contra estos fallos), igual que tampoco lo hace limitar la longitud del patrón.
El proyecto descubrió el 26 de octubre que la instancia seguía siendo vulnerable. Preguntado por RuntimeWire por lo que le puso sobre la pista, Natanael Copa, desarrollador principal de Alpine, respondió «LLM agent» (un agente basado en un modelo de lenguaje), según recoge el medio el 29 de octubre. Copa espera que otros equipos que construyen sus propias imágenes de GitLab se encuentren con el mismo hueco. El 27 de octubre, a las 06:12 UTC, Alpine volvió a publicar la etiqueta 19.3.6 en Docker Hub, esta vez con Ruby 3.3.13 dentro. Cualquier imagen 19.3.6 descargada antes de esa fecha, y cualquier etiqueta anterior, sigue siendo vulnerable. Los paquetes ruby de la propia distribución ya llevan la corrección: Alpine 3.22, 3.23, 3.24 y edge incluyen Ruby 3.4.12 y Alpine 3.21 incluye Ruby 3.3.13.
Del parche del 12 de octubre a la reconstrucción del 28
Los hitos salen del comunicado de Alpine, de la nota de Ruby y del registro de etiquetas de Docker Hub. Las horas constan solo donde la fuente las da.
Fecha y hora | Hecho |
|---|---|
12 de octubre de 2026 | GitLab publica 19.2.8, 19.3.5 y 19.4.2 con la corrección de CVE-2026-97229 en su Ruby empaquetado |
13 de octubre, 11:25 UTC | Alpine publica alpinelinux/gitlab:19.3.4 y actualiza su instancia a esa versión, aún vulnerable, en lugar de a la 19.3.5 |
20 de octubre | Una cuenta recién registrada empieza a sondear gitlab.alpinelinux.org |
21 de octubre | Ataque principal con configuraciones de CI manipuladas: ocho caídas del worker, errores 502 y trabajos de CI fallidos |
22 de octubre | Alpine actualiza a GitLab 19.3.6, que no corrige el fallo porque la imagen usa el Ruby estándar |
23 de octubre | Alpine bloquea la cuenta del atacante. Ruby publica 3.3.13, 3.4.12 y 4.0.9 con la corrección |
24 de octubre | Alpine verifica aports, los paquetes y las releases del 21 de octubre contra registros externos |
25 y 26 de octubre | El atacante vuelve a intentarlo y fracasa |
26 de octubre | Alpine descubre que la instancia sigue siendo vulnerable tras la actualización |
27 de octubre, 06:12 UTC | Nueva imagen alpinelinux/gitlab:19.3.6 con Ruby 3.3.13 en Docker Hub. GitLab se cierra a todos salvo desarrolladores y servidores de compilación y empieza la reconstrucción |
28 de octubre, 21:27 UTC | Reconstrucción terminada, contraseñas reiniciadas y claves secretas sustituidas. Alpine publica el comunicado y lo anuncia en Fosstodon |
Con la ficha del fallo pasa algo parecido a lo de la cronología: los datos son los mismos que publicamos el 13 de octubre, pero el estado de explotación ha cambiado. CISA no lo había incluido en su catálogo KEV a 29 de octubre de 2026, con la versión 2026.10.28 del catálogo, y NVD lo tiene publicado desde el 13 de octubre con la puntuación del CNA de GitLab.
Dato | Valor |
|---|---|
CVE | CVE-2026-97229 (CWE-190, desbordamiento de entero) |
CVSS y vector | 10,0 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H), asignado por GitLab como CNA y publicado en NVD el 13 de octubre de 2026 |
Producto | GitLab Community Edition y Enterprise Edition autoalojados, incluidos los compilados desde el código fuente o en imágenes propias con Ruby sin parchear |
Versiones afectadas | 19.2 anteriores a 19.2.8, 19.3 anteriores a 19.3.5 y 19.4 anteriores a 19.4.2. Con Ruby propio, cualquier versión que ejecute Ruby anterior a 3.3.13, 3.4.12 o 4.0.9 |
Versiones corregidas | 19.2.8, 19.3.5 y 19.4.2 (12 de octubre de 2026) en paquetes e imágenes de GitLab. Ruby 3.3.13, 3.4.12 y 4.0.9 (23 de octubre de 2026) para instalaciones con Ruby propio |
Explotación activa | Intento confirmado por Alpine Linux los días 20, 21, 25 y 26 de octubre de 2026, sin ejecución de código demostrada. No incluido en el catálogo KEV de CISA a 29 de octubre de 2026 |
Gravedad | Crítica |
Quién tiene que actuar, de las cuentas de Alpine a los GitLab con Ruby propio
El primer grupo es el de los usuarios de gitlab.alpinelinux.org, desarrolladores y colaboradores que tendrán que restablecer su contraseña cuando la instancia reabra y cambiarla donde la hubieran reutilizado. El segundo es el de quien despliega la imagen alpinelinux/gitlab, que debe volver a descargar la 19.3.6 publicada el 27 de octubre. El tercero, y el más difícil de contar, es el de quien ejecuta GitLab compilado desde el código fuente o en una imagen propia con un Ruby distinto del que empaqueta GitLab. Para ellos ninguna versión de GitLab basta por sí sola, y el parche real se llama Ruby 3.3.13, 3.4.12 o 4.0.9.
Cuánto parque autoalojado sigue expuesto no lo mide ninguna fuente con esa granularidad. Censys contaba el 13 de octubre 25.766 hosts con versiones vulnerables, un 31 % de los 82.454 servidores GitLab que detecta, y 79.224 propiedades web, un 39 % del total. Alemania encabezaba el mapa con 6.469 hosts, seguida de Rusia con 4.173, Estados Unidos con 2.927, Francia con 2.078 y China con 1.134. España no aparece entre los diez primeros países de la figura y, en el momento de escribir esta noticia, no hay recuento público de instancias españolas. El ángulo español lo pone INCIBE-CERT, que calificó el fallo de crítico en su aviso INCIBE-2026-730 del 13 de octubre.

Los recuentos de Censys identifican la versión de GitLab, no la versión de Ruby que hay debajo, y ahí está el matiz que el caso de Alpine deja al descubierto. Un servidor que anuncia 19.3.6 cuenta como parcheado para cualquier escáner, para el catálogo de INCIBE-CERT y para el propio panel de administración de GitLab, y puede seguir siendo explotable si alguien lo construyó con su propio intérprete. Ya vimos algo parecido con las copias de Brotli que nadie declaró en servidores, imágenes y binarios. La diferencia es que aquí la dependencia escondida es el intérprete entero de la aplicación.
Qué revisar hoy en un GitLab autoalojado con Ruby propio
GitLab ofrece en su nota de detección estos filtros sobre el registro de Workhorse, que sobrevive a la muerte del worker y recoge el 502 que la petición de lint deja detrás. El tercero busca la laguna de correlación, es decir, un 502 cuyo identificador no aparece en api_json.log porque el worker murió antes de escribirlo.
WH=/var/log/gitlab/gitlab-workhorse/current
RAILS=/var/log/gitlab/gitlab-rails/api_json.log
# Crashed/hung backend on lint or GraphQL routes
grep -E '"uri":"[^"]*(ci/lint|/api/graphql)' "$WH"* | grep -E '"status":502|badgateway'
# Correlation gap: a 502 whose correlation_id is missing from the Rails API log
# (worker died before logging) is the strongest confirmation
grep -E '"uri":"[^"]*(ci/lint|/api/graphql)' "$WH"* | grep '"status":502' \
| grep -o '"correlation_id":"[^"]*"' | cut -d'"' -f4 | sort -u | while read -r cid; do
grep -q "$cid" "$RAILS"* || echo "MISSING from api_json (worker died mid-request): $cid"
done
# Large-carrier crashes (~2 MB request body), high-confidence subset
grep -E '"uri":"[^"]*(ci/lint|/api/graphql)' "$WH"* | grep '"status":502' \
| grep -oE '"read_bytes":[0-9]+' | awk -F: '$2>1000000'Con esos filtros a mano, el orden de trabajo para una instancia autoalojada queda así.
- Averigua cómo está construido tu GitLab. Si es un paquete Omnibus o la imagen oficial, las versiones 19.2.8, 19.3.5 y 19.4.2 del 12 de octubre ya llevan el Ruby parcheado, y las 19.2.9, 19.3.6 y 19.4.3 del 21 cierran también CVE-2026-110264. Si es una instalación compilada desde el código fuente o una imagen propia, comprueba el intérprete con el comando de abajo y exige 3.3.13, 3.4.12 o 4.0.9 como mínimo.
- Si usas alpinelinux/gitlab, vuelve a descargar la etiqueta 19.3.6 y confirma que el digest de la imagen empieza por f299e16a8b74, el que Docker Hub muestra para la publicación del 27 de octubre.
- Pasa los filtros de GitLab sobre el registro de Workhorse desde el 12 de octubre y busca en puma_stderr.log caídas con la marca BUG cuyo rastreo cite onig_compile, compile_tree o regexp_matcher.rb. Un 502 en ci/lint o en /api/graphql cuyo identificador falte en api_json.log se trata como un intento de ejecución de código.
- Lista las cuentas creadas desde el 12 de octubre y los proyectos públicos nuevos con ficheros .gitlab-ci.yml y rutas con caracteres fuera de ASCII. Mientras no tengas el Ruby corregido, desactiva el registro abierto y la creación de proyectos públicos por usuarios no fiables, la mitigación provisional que Rapid7 propuso el 13 de octubre.
- Si encuentras una caída o un 502 huérfano, haz lo que ha hecho Alpine: rota los secretos de gitlab-secrets.json, todas las variables de CI/CD, los tokens de los runners y los tokens de acceso, e invalida las contraseñas. Una gestión de identidades y accesos con tokens de vida corta reduce lo que hay que rotar.
- Activa el registro de conexiones salientes del servidor y de las conexiones a la base de datos, y guarda los registros de GitLab más de catorce días. Alpine no pudo cerrar la investigación por carecer de ambas cosas, y nuestras notas sobre las reglas Sigma y sobre la gestión centralizada de logs cubren cómo montar esa retención.
- Notifica cualquier indicio a INCIBE-CERT si eres empresa o ciudadano, o al CCN-CERT si perteneces al sector público.
Este comando comprueba qué intérprete ejecuta de verdad la aplicación en una imagen propia, que es lo que a Alpine se le pasó durante cuatro días.
# Versión de Ruby que usa el proceso de GitLab dentro del contenedor
docker exec gitlab sh -c 'ruby -v; ls /proc/$(pgrep -o -f puma)/exe'
# Versión de GitLab en la misma imagen
docker exec gitlab cat /opt/gitlab/embedded/service/gitlab-rails/VERSION 2>/dev/null \
|| docker exec gitlab cat /home/git/gitlab/VERSIONAlpine ha anunciado cuatro cambios tras el incidente: un Ruby con la corrección en la instancia reconstruida, aplicar las versiones de seguridad críticas de GitLab en menos de un día y revisar cada una por si trae parches de Ruby u otros componentes que su build deba aplicar aparte, registrar las conexiones salientes y las de base de datos con una retención superior a catorce días, y firmar las etiquetas de release para que cualquiera pueda verificarlas sin depender de GitLab. La última medida es la que cambia la posición de quien construye contenedores sobre Alpine, porque hasta ahora la verificación de una release terminaba en el mismo servidor que acaba de ser reconstruido.




No hay comentarios todavía