Entre el 23 de septiembre y el 21 de octubre, GitLab publicó tres tandas de parches críticos que corrigen cuatro vulnerabilidades distintas. Las cuatro las encontró la misma persona. Las cuatro están en el mismo sitio: Onigmo, el motor de expresiones regulares que Ruby lleva dentro y que GitLab empaqueta junto con su propio intérprete.
Eso tiene dos consecuencias prácticas, y ninguna de las dos cabe en la frase «ya lo hemos parcheado». La primera es que cada parche dejó la versión que acababas de instalar dentro del rango afectado del aviso siguiente, tres veces seguidas. La segunda es que el arreglo del último no es una línea del código de GitLab: es un parche al intérprete, y eso cambia dónde hay que mirar para saber si te alcanza.
Cuatro CVE, tres tandas, un motor
Todo lo que sigue sale de las tres notas de parche publicadas por GitLab. El investigador aparece acreditado con el alias joaxcar en las tres, siempre a través del programa de recompensas de la empresa en HackerOne.
| Tanda | Qué corrige | A quién alcanza |
|---|---|---|
| 23 sep 19.4.1 / 19.3.3 / 19.2.7 |
CVE-2026-89078 (9,9) CVE-2026-93577 (9,9) Los dos con PR:L: hace falta cuenta. |
19.2, 19.3, 19.4 |
| 12 oct 19.4.2 / 19.3.5 / 19.2.8 |
CVE-2026-97229 (10,0) Ya PR:N. En KEV desde el 29. |
19.2, 19.3, 19.4 |
| 21 oct 19.5.1 / 19.4.3 / 19.3.6 / 19.2.9 / 19.1.9 / 19.0.10 / 18.11.13 |
CVE-2026-110264 (10,0)PR:N. No está en KEV. |
«all versions from 7.0 before 18.11.13», más las seis ramas 19.x por debajo de su corrección. Una década de versiones. |
La columna del medio es la que cuenta la historia, y esta lectura es nuestra. En septiembre los dos fallos exigían una cuenta: el vector llevaba PR:L, privilegios bajos pero privilegios. En octubre, dos veces, el mismo investigador volvió al mismo compilador y ese requisito desapareció. Lo que se ve en la tabla es alguien recorriendo sistemáticamente una superficie que estaba sin explorar, y encontrando cada vez una entrada más barata.
La cinta de correr de las versiones
Esto es aritmética de los rangos oficiales. Si actualizaste el 23 de septiembre te quedaste en la 19.4.1, y el aviso del 12 de octubre declara afectadas las «19.4 before 19.4.2». Si entonces actualizaste a la 19.4.2, el aviso del 21 declara afectadas las «19.4 before 19.4.3». Tres tandas, y después de cada una la versión recién instalada vuelve a aparecer en la lista. Lo mismo con las ramas 19.3 y 19.2, con sus números respectivos.
No hay nada que reprochar a quien parcheó rápido: hizo lo correcto las tres veces. El problema es que «ya está parcheado» tiene fecha de caducidad y nadie la escribe en ningún sitio. Contra esto no protege reaccionar más deprisa al siguiente titular, sino tener una cadencia escrita para esa máquina concreta: una ventana periódica, un responsable con nombre y una comprobación posterior de que la ventana se cumplió. Es la parte menos vistosa del mantenimiento informático, y es la que convierte un aviso en una tarea programada.
Un campo de 16 bits
La explicación más clara del último fallo no está en ninguna nota de prensa. Está en la petición de cambio con la que GitLab incorporó el arreglo a su propio empaquetado, escrita para quien tenía que revisarla: «Onigmo stores range-repeat and null-check IDs in a signed 16-bit bytecode operand but counts them in an int, so a regexp with more than 32,767 of either wraps the ID negative. At match time the negative ID indexes the repeat stack, an out-of-bounds write».
Traducido: Onigmo numera internamente las repeticiones de un patrón. Cuenta esos números en un entero grande, pero los guarda en un campo de 16 bits con signo, así que pasando de 32.767 el identificador se da la vuelta y se queda en negativo. Más tarde ese número negativo se usa como índice para acceder a una pila, y ahí se escribe fuera de sitio. El arreglo rechaza el patrón al compilarlo, con un error, en lugar de aceptarlo. Conviene decir que nadie escribe por accidente una expresión regular con 32.768 repeticiones: hay que escribirla a propósito y hay que tener dónde meterla.
Dónde vive cada arreglo
Aquí hay una distinción que merece la pena hacer bien, porque los dos casos no son iguales. El del 12 de octubre lo cerró GitLab en su lado: su propia descripción dice que el problema era «insufficient regular expression validation», y se corrigió validando antes de compilar. El del 21 es otra cosa. Su arreglo es un fichero en config/patches/ruby/ dentro de omnibus-gitlab, uno por cada Ruby empaquetado (3.3, 3.4 y 4.0), que aplica tal cual un commit del propio Ruby.
La razón de que GitLab cargue con ese parche la da la propia petición de cambio: el arreglo original estaba sólo en la rama principal de Ruby, la futura 4.1, y no en las ramas 3.3, 3.4 ni 4.0 que usa todo el mundo. Ruby publicó sus versiones corregidas dos días después, el 23 de octubre: 4.0.9, 3.4.12 y 3.3.13, que cierran los fallos de Onigmo detrás de los cuatro CVE de esta historia. Hay un detalle revelador en la misma petición: en las variantes para 3.3 y 3.4 el hueco donde entran los nuevos códigos de error ya estaba ocupado por otro parche de Ruby anterior, el del tamaño del programa compilado, y en la de 4.0 no. Es decir, que este no era el primero.
La consecuencia operativa es corta. Si tu GitLab es el paquete Omnibus o la imagen oficial de contenedor, el intérprete viene dentro y actualizar GitLab te trae el parche; esa es la mayoría de las instalaciones y con eso basta. Si tu GitLab se instaló desde fuentes contra el Ruby del sistema, actualizar GitLab no toca ese intérprete, y la frontera pasa a ser 4.0.9, 3.4.12 o 3.3.13. Si además ese Ruby lo empaqueta tu distribución, lo que decide es la versión que tu distribución sirve hoy. Es el caso minoritario, y es donde el inventario se queda corto: la fila dice «GitLab 19.5.1» y el componente que falla está una capa más abajo.
Y el resto de tus aplicaciones en Ruby
Los cuatro CVE llevan el nombre de GitLab porque allí había un programa de recompensas donde reportarlos. El motor es de Ruby y está debajo de cualquier aplicación Rails que tengas en marcha. Conviene acotar enseguida qué significa eso, porque la versión alarmista es falsa: la condición para que te afecte es compilar una expresión regular a partir de texto que venga de fuera, y la mayoría de las aplicaciones no lo hacen, porque sus patrones están escritos en el código y no los elige nadie desde el navegador. GitLab sí lo hace, por diseño, porque su configuración de CI/CD deja escribir expresiones.
Lo útil, entonces, es una pregunta que no suele estar en ninguna lista: ¿alguna de nuestras aplicaciones compila expresiones regulares con texto que viene de fuera? Un buscador con comodines, un filtro de reglas que configura el cliente, un validador que acepta un patrón por formulario. Tu escáner de vulnerabilidades tampoco la contesta, porque compara nombres y versiones de paquete, y aquí lo que falla es el intérprete que ejecuta el paquete. Es la tercera vez que escribimos sobre esta forma concreta de punto ciego: ya pasó con seis avisos y un escáner que sigue diciendo cero y, en el mismo mundo de Ruby, con la biblioteca que nadie eligió.
Lo que esto no es
No es la historia de un fabricante negligente. GitLab parcheó su propio intérprete, publicó la petición de cambio en abierto con el commit de origen y el razonamiento entero, y tramitó los cuatro reportes por su programa de recompensas. Nos gustaría que más fabricantes dejaran leer eso: la mayoría publica una línea y un número de versión.
Tampoco hay constancia pública de que el último se esté explotando. El que CISA dio de alta el 29 de octubre, junto con un fallo de Cisco y bajo la directiva BOD 26-04, es el del 12. Según las cifras que la Shadowserver Foundation publicó el 2 de noviembre, de las unas 39.000 instancias de GitLab que ve asomadas a internet cada día, más de 1.000 siguen sin el parche del 12 de octubre y más de 11.300 sin el del 21. Esa segunda cifra es exposición, no incidentes. Y conviene no explicarla con una sola causa: el parche del 21 es nueve días más joven y no tiene un catálogo oficial detrás empujando, además de alcanzar muchísimas más versiones.
Y el «sin autenticar» de los avisos de octubre tiene letra pequeña. Rapid7 validó la cadena completa contra la imagen oficial de GitLab CE 19.4.1 y describe así lo que hace falta: «an attacker must first be able to commit a malicious CI/CD configuration file .gitlab-ci.yml, and a repository path containing UTF-8 characters to a branch in a public project». Eso no cambia que haya que parchear. Cambia cuántas horas tienes para hacerlo, y la respuesta depende de a quién deje tu instancia crear proyectos. Por cierto, la 19.4.1 contra la que Rapid7 lo reprodujo era la versión corregida de la tanda anterior.
El conflicto de interés, por delante: nuestro propio CI/CD corre sobre una instancia de GitLab autoalojada, con despliegues GitOps a Docker Swarm y Kubernetes. Esta cronología la hemos recorrido como cualquiera.
La fila que le falta a tu inventario
En vez de una lista de comprobaciones, una tabla de dos columnas: lo que tu inventario dice hoy de esa máquina y lo que tendría que poder contestar. Las tres primeras filas se resuelven con un comando. La última no, y es la que decide.
| Lo que dice hoy | Lo que tiene que poder contestar |
|---|---|
| «GitLab 19.4.2» | ¿Está en la lista del 21 de octubre o se quedó en la del 12? La frontera es 19.5.1 / 19.4.3 / 19.3.6 / 19.2.9 / 19.1.9 / 19.0.10 / 18.11.13. |
| (nada) | ¿El intérprete lo trae el paquete de GitLab o es el del sistema? Un ruby -v en la máquina, y la frontera 4.0.9 / 3.4.12 / 3.3.13. |
| «Servidor de desarrollo» | ¿Qué puede firmar esa máquina? Llaves de despliegue, tokens de registro de ejecutores, variables de CI, acceso al registro de contenedores. |
| (nadie) | ¿Quién tiene nombre y apellidos para esa máquina, y cuándo es su próxima ventana? |
Cuando la respuesta a «de dónde salió este paquete» es «lo montó alguien que ya no está», lo que toca primero es reconstruir la procedencia y después decidir si esa máquina sigue siendo artesanal o pasa a un empaquetado con soporte. Esa decisión no la vendemos con una licencia debajo, porque no revendemos ninguna, y bastantes veces acaba en «déjalo como está y ponle una ventana»; es el trabajo de consultoría. Que la ventana exista todos los meses y que alguien compruebe después que se cumplió es mantenimiento.
Cinco semanas, cuatro vulnerabilidades y un campo de 16 bits dentro de un intérprete que nadie eligió a conciencia. La puntuación del CVE no distingue entre el equipo al que esto le costó una tarde y el equipo al que le costó una madrugada. Lo que los distingue es si alguien tenía escrito, antes del 23 de septiembre, de quién era esa máquina y qué día le tocaba.
Fuentes. Las tres tandas salen de las notas de parche del propio fabricante, consultadas el 3 de noviembre de 2026: la del 23 de septiembre (19.4.1), con CVE-2026-89078 y CVE-2026-93577, ambos 9,9 y con vector PR:L; la del 12 de octubre (19.4.2), con CVE-2026-97229, 10,0, vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H; y la del 21 de octubre (19.5.1), con CVE-2026-110264, 10,0 y el mismo vector. De ahí proceden también las versiones, los rangos afectados, las descripciones citadas y el crédito a joaxcar. El alta en el catálogo KEV, el CVE de Cisco del mismo día y la directiva BOD 26-04 son del aviso de CISA del 29 de octubre de 2026. La precondición de explotación y la validación contra la imagen 19.4.1 son del análisis de Rapid7. La explicación del desbordamiento, el fichero de parche y el motivo de aplicarlo a 3.3, 3.4 y 4.0 son de la petición de cambio 9921 de omnibus-gitlab. Las cifras de instancias las atribuye a la Shadowserver Foundation Techzine, el 2 de noviembre de 2026, y son una medición de terceros, no un recuento nuestro. Las citas técnicas se dejan en su inglés original para que se puedan buscar literalmente. La lectura de la caída de PR:L a PR:N como superficie en exploración es nuestra y va marcada como tal en el texto. Ya tratamos CVE-2026-97229 el 14 de octubre desde otro ángulo, el de la reconstrucción de la instancia, en el post que le dedicamos el 14 de octubre. La fotografía de la imagen social es «Cable closet bh», de dominio público, vía Wikimedia Commons.
¿Quién tiene nombre y apellidos para vuestro GitLab?
Lo miramos contigo: qué versión corre de verdad, de dónde salió el paquete, qué puede firmar esa máquina y qué ventana mensual tiene escrita. Sin vender licencias de nada.
Hablar con everyWAN