DevOps

Ruby lanza actualizaciones para corregir fallos críticos en Onigmo

Un regex compilado con input del usuario no debería poder tumbar un servidor. El 23 de octubre de 2026 Ruby publicó tres versiones (4.0.9, 3.4.12 y 3.3.13) que corrigen exactamente eso: tres defectos de memoria en Onigmo, el motor de expresiones regulares que viene compilado dentro del intérprete. GitLab los encontró mientras endurecía su CI/CD, con el bug bounty de la casa tirando del hilo, los tapó en releases que salieron entre el 23 de septiembre y el 21 de octubre y les colgó cuatro identificadores CVE. El resto de los que corremos Ruby, aunque nunca toquemos GitLab, arrastramos el mismo motor en producción.

El detalle que me dejó pensando no fue el CVSS 10.0: fue el patrón de versiones. El arreglo del CVE-2026-110264 no entró en Ruby 4.0.8, que salió un día antes, así que hay que instalar 4.0.9 sí o sí. Y en las series 3.4 y 3.3 ninguna versión anterior a 3.4.12 y 3.3.13 lleva los tres parches: los mantenedores metieron el backport de golpe, el mismo día. Si tienes Ruby 3.4.11 corriendo en un servidor, estás expuesto a los cuatro.

TL;DR — Lo que vas a tener al final

  • Tiempo de setup: 15 minutos para el diagnóstico. Entre 10 y 60 minutos para el upgrade, según si usas paquetes del sistema, rbenv, asdf o imágenes de contenedor.
  • Requisitos: acceso root (o sudo) al servidor, una terminal y saber cómo instalaste Ruby en esa máquina. No hace falta recompilar nada a mano.
  • Resultado: tu servidor con Ruby 4.0.9, 3.4.12 o 3.3.13 (o superior), un criterio claro para saber si tu aplicación es vulnerable y el grep que encuentra la superficie expuesta.
  • Troubleshooting: lo más común es estar en 4.0.8, que no cubre el último CVE, o en una serie 3.3/3.4 anterior a .12/.13. Revisa también si tu Dockerfile fija una versión vieja de la imagen base.

Los cuatro CVE no son de Ruby

Un punto que confunde a todo el mundo: los cuatro CVE son de GitLab, no de Ruby. Ruby declaró que no solicitó identificadores CVE para estos fixes (por ahora) y pidió explícitamente que los escáneres usen los identificadores de GitLab. Si buscas “CVE de Ruby” para estos números no los vas a encontrar en la rama de Ruby: en NVD aparecen con sourceIdentifier [email protected] y las métricas de CVSS que ves son aportadas por GitLab como CNA, no puntuaciones primarias de NVD.

El fondo técnico es este: GitLab compila las expresiones regulares que le escriben sus usuarios, por ejemplo los patrones dentro de la configuración de CI, con Regexp.new. Al probar el compilador de regex de Ruby, GitLab descubrió que un patrón construido a mano puede corromper memoria durante la propia compilación, y que eso se puede convertir en ejecución remota de código en el servidor de GitLab. El fallo, en cambio, sí estaba en el motor de Ruby: lo que se rompe es su compilador de regex, el mismo que GitLab usa por debajo. Y el hallazgo tiene nombre: Johan Carlsson (joaxcar) descubrió el desbordamiento de entero que corrigió el PR #19020 a través del programa de bug bounty de GitLab.

CVE Qué provoca CVSS v3.1 Vector Quién lo dispara
CVE-2026-89078 Double free al parsear un patrón que falla después de una alternancia 9.9 AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L Usuario autenticado
CVE-2026-93577 Integer overflow en el tamaño del patrón compilado 9.9 AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H Usuario autenticado
CVE-2026-97229 Integer overflow en el compilador de regex de CI/CD 10.0 AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H Sin autenticación
CVE-2026-110264 Overflow de IDs internos en un patrón con muchas repeticiones 10.0 AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H Sin autenticación

Los dos CVEs de 9.9 apuntan a un atacante que ya tiene una sesión abierta. Los dos de 10.0 no necesitan cuenta, y son los que GitLab tardó más en parchear: el 12 y el 21 de octubre, en dos tandas separadas. Y CVE-2026-110264 tiene el alcance más serio de todos, porque afecta a toda versión de GitLab desde la 7.0 que no tenga el parche.

Sobre el lado de Ruby, la superficie real es más estrecha de lo que parece. Según el aviso oficial, los bugs solo se alcanzan cuando una aplicación compila una expresión regular a partir de un string que el atacante controla: pasándolo a Regexp.new, Regexp.union o Regexp.compile, o interpolándolo dentro de un literal como /#{input}/. Las aplicaciones que solo usan regex escritas por sus propios developers no están afectadas. Ese es el matiz que separa el incidente de GitLab de una urgencia general: si tu app nunca compila una regex con datos externos, no hay nada que hacer más allá de actualizar por higiene.

Qué versión de Ruby arregla qué

Esta es la tabla que importa, y sale textual del aviso oficial. La columna “Ruby 4.0” indica la primera versión de esa serie que ya incluye el fix, no la que se publicó ese día:

CVE de GitLab Bug en Ruby Cambio Ruby 4.0 Ruby 3.4 Ruby 3.3
CVE-2026-89078 Double free al parsear un patrón que falla tras una alternancia PR #13332 4.0.0 3.4.12 3.3.13
CVE-2026-93577 y CVE-2026-97229 Integer overflow en el tamaño del patrón compilado PR #19020 4.0.8 3.4.12 3.3.13
CVE-2026-110264 Overflow de IDs internos en un patrón con muchas repeticiones PR #16801 4.0.9 3.4.12 3.3.13

De ahí salen tres conclusiones prácticas. Primera: Ruby 4.0.8 no sirve, aunque se haya publicado el 22 de octubre y suene a “la última”. Te falta el fix de #16801, que es justamente el CVE de 10.0 sin autenticación. Segunda: 3.4.12 y 3.3.13 son las primeras versiones de sus series con cualquiera de los tres fixes, así que 3.4.0 a 3.4.11 y 3.3.0 a 3.3.12 quedaron expuestos al double free. Tercera: Ruby 3.2 no recibe nada. Esa serie quedó en EOL el 1 de abril de 2026 y su última versión fue 3.2.11, cuya nota de publicación ya recomendaba migrar a 3.4 o 4.0.

Un dato sobre la política de versiones que vale la pena tener en cuenta: la serie 3.3 está en mantenimiento de seguridad, donde normalmente solo entran fixes de vulnerabilidades. El release 3.3.13 es una excepción explícita, precisamente porque incluye los arreglos de compilación de regex de los que dependen los CVE de GitLab. Aprovecho para mencionar que ese mismo 3.3.13 también actualizó la gema resolv a 0.3.2 y corrigió CVE-2026-80212 y CVE-2026-80213, así que actualizar a 3.3.13 te cubre dos frentes.

La trampa de Ruby 4.0.8

Vale la pena mirar la cronología de los backports porque explica por qué hay tanta gente expuesta sin saberlo. El fix del double free (PR #13332) se fusionó en la rama master de Ruby el 16 de mayo de 2025. Eso significa que Ruby 4.0.0 ya salió con ese fix dentro y las 4.0.x posteriores lo mantuvieron. Pero el backport a las series 3.4 y 3.3 no llegó hasta octubre de 2026, en los mismos releases que traían los otros dos parches.

Es decir: quien llevaba meses en 3.4.11 compilando regex de input externo estuvo con el double free abierto todo ese tiempo, y quien actualizó a 3.4.12 de golpe se llevó los tres fixes de un solo golpe. Por eso el aviso recomienda “Ruby 4.0.9, 3.4.12 or 3.3.13 or later” y no “the latest”: en la serie 4.0 la última no siempre es la completa, y en las series 3.4 y 3.3 la última sí lo es, pero solo porque los mantenedores metieron el backport completo en un solo release.

Hay un efecto secundario interesante. El propio PR #13332, que arreglaba el double free, introdujo una regresión de fuga de memoria en Ruby 4.0. Se reportó como bug #22384 en el tracker de Ruby el 25 de septiembre de 2026 y lo corrigió el PR #19026, que entró en 4.0.8. El síntoma era invisible salvo mirando RSS: las versiones 4.0.6 y 4.0.7 filtraban alrededor de 134 bytes por cada compilación fallida de un patrón con alternancia, y el reporte documenta el impacto en Rails 7.1+ y 8.x, donde config.filter_parameters con una regex hacía que cada request perdiera cerca de 128 bytes por worker. El tracker marca el backport a 3.3 y 3.4 como innecesario, porque la regresión nunca existió ahí: el código problemático jamás estuvo en esas ramas. La moraleja es que un fix de seguridad bien revisado puede traer su propio bug, y por eso toca leer las notas de release en vez de asumir que el número de versión habla por sí solo.

Verifica la versión de Ruby que tienes

Antes de cambiar nada, mira qué tienes. Estos tres comandos te dicen la versión del intérprete, la de RubyGems y la del runtime que carga la aplicación:

ruby -v
gem -v
ruby -e 'puts RUBY_VERSION'

Si quieres el veredicto automático en vez de comparar a ojo, este one-liner te dice si la versión activa ya tiene los tres fixes. Acepta 4.0.9 o superior en la serie 4.0, 3.4.12 o superior en 3.4 y 3.3.13 o superior en 3.3:

ruby -e 'v = RUBY_VERSION.split(".").map { |x| x.to_i }; ok = (v[0] == 4 && v[1] == 0 && v[2].between?(9, 99)) || (v[0] == 4 && v[1].between?(1, 99)) || (v[0] == 3 && v[1] == 4 && v[2].between?(12, 99)) || (v[0] == 3 && v[1] == 3 && v[2].between?(13, 99)) || v[0].between?(5, 99); puts(ok ? "PARCHEADO" : "VULNERABLE - actualiza")'

Si tienes varios Ruby instalados, la ruta importa: which -a ruby te muestra el orden de resolución y a veces el que está activo no es el que crees. Y si usas un gestor de versiones, el listado te dice de una vez cuáles tienes disponibles:

which -a ruby
if command -v rbenv > /dev/null 2>&1; then rbenv versions; fi
if command -v asdf > /dev/null 2>&1; then asdf list ruby; fi

En distribuciones con paquetes, apt-cache policy te dice qué versión tienes instalada y cuál es la candidata disponible en el repositorio. Ojo con esto: los repositorios de distribución van detrás. El fix de la serie 3.3 o 3.4 llega al repositorio meses después del release upstream, y en el caso de 3.2 nunca llega porque la serie está muerta. Si el repo te ofrece una versión anterior a la parcheada, no te sirve instalar eso: toca compilar, usar rbenv/asdf, o llevar la aplicación en un contenedor con la imagen oficial correcta.

apt-cache policy ruby
apt-cache madison ruby

Busca en tu código dónde se compila regex de input externo

Esta es la parte que separa “tengo Ruby viejo” de “soy vulnerable”. Si tu aplicación nunca compila una regex con datos de fuera, el upgrade es higiene y nada más. El grep te da la lista exacta de los puntos donde una regex se construye en runtime:

grep -rnE 'Regexp[.](new|union|compile)' --include='*.rb' /ruta/de/tu/app
grep -rnF '/#{' --include='*.rb' /ruta/de/tu/app

El primer comando busca las tres APIs que menciona el aviso oficial de Ruby. El segundo busca interpolación dentro de un literal de regex, que es la forma más común de meter input externo sin darse cuenta. Si te salen resultados, el siguiente paso no es hacer un upgrade sino cambiar ese código para que la entrada nunca se interprete como patrón.

La versión responsable de ese cambio es validar el input antes de compilar y, cuando el valor es un literal de texto y no un patrón, escaparlo con Regexp.escape:

def patron_seguro(entrada)
  raise ArgumentError, "entrada demasiado larga" if entrada.length > 100
  raise ArgumentError, "caracteres no permitidos" unless entrada.match?(/^[a-zA-Z0-9_]+$/)
  Regexp.new(entrada)
end

Ese helper es deliberadamente conservador: longitud máxima, conjunto de caracteres permitido y solo después compila. Si tu caso real necesita metacaracteres, no uses este helper tal cual: lo importante es que la validación ocurre antes de Regexp.new, no después.

Actualiza el contenedor y no dejes la versión vieja pegada en la imagen

Buena parte de los servidores Ruby que hay en producción no ejecutan el intérprete del sistema, sino el de una imagen de contenedor. Ahí el problema clásico es el FROM ruby:3.3.8 congelado en el Dockerfile: actualizar la imagen base en el registro no hace nada si tu build sigue pidiendo un tag viejo.

docker images --format '{{.Repository}}:{{.Tag}}' | grep -i ruby
docker ps --format '{{.Names}} {{.Image}}'

Las imágenes oficiales de Ruby ya publicaron los tags parcheados el mismo 23 de octubre: ruby:4.0.9, ruby:3.4.12 y ruby:3.3.13, cada uno con sus variantes -slim, -alpine y por distribución base. Reconstruir contra esos tags es la forma más limpia de cerrar el CVE, y de paso te obliga a revisar si tu Dockerfile fija la versión o deja que flote con latest, que tampoco es una política de actualización.

Y para verificar qué hay realmente dentro del contenedor en ejecución, que no siempre es lo mismo que el tag de la imagen:

docker exec NOMBRE_CONTENEDOR ruby -v

Si tu despliegue pasa por CI/CD, este es el momento de revisar también el pipeline. Recuerda que los archivos de configuración de CI son superficie de ataque: los patrones que se compilan ahí son justamente los que permiten la explotación en GitLab. En la guía de GitLab CI/CD para sysadmins tienes el contexto completo de esa superficie.

Si tu GitLab es autoalojado, actualiza el servidor también

Acá hay una distinción importante: el fix de Ruby arregla el bug de fondo, pero los CVE que se publicaron son de GitLab, y el parche que GitLab publicó está en sus propias releases. Si eres el administrador de una instancia self-hosted, tienes que actualizar las dos cosas.

Primero, ver el estado de los servicios y la versión del entorno. gitlab-rake gitlab:env:info es la tarea oficial de diagnóstico y entre otras cosas imprime la versión de Ruby que usa la instancia, que es justo el dato que necesitas para confirmar si el parche de Ruby ya está dentro:

sudo gitlab-ctl status
sudo gitlab-rake gitlab:env:info

Las versiones parcheadas de GitLab, según las descripciones de NVD, son: 19.2.7, 19.3.3 y 19.4.1 para CVE-2026-89078 y CVE-2026-93577; 19.2.8, 19.3.5 y 19.4.2 para CVE-2026-97229; y 18.11.13, 19.0.10, 19.1.9, 19.2.9, 19.3.6, 19.4.3 y 19.5.1 para CVE-2026-110264, que es el que arrastra el alcance desde GitLab 7.0.

Para instalar una versión concreta desde el paquete de Linux, la forma documentada es fijar el número de versión en el apt install. Tomando como ejemplo la 19.4.3, que ya incluye el CVE-2026-110264:

sudo apt update && sudo apt install gitlab-ce=19.4.3-ce.0
sudo gitlab-ctl reconfigure
sudo gitlab-ctl restart

Ojo con un detalle del proceso: si tu ruta de upgrade tiene pasos intermedios obligatorios, no puedes saltar directo a la última versión mayor. Revisa la ruta de upgrade de tu rama antes de fijar el número, porque el instalador te va a frenar igual. Y toma un backup antes de tocar nada: sudo gitlab-ctl backup create es el comando de siempre para una instancia Omnibus.

Si además tienes otros CVE de GitLab en la cabeza, el de GitLab AI Gateway (CVE-2026-90970, CVSS 9.9) es independiente de este y tiene su propio parche.

Qué no te protege

El aviso de Ruby es explícito en dos puntos que mucha gente asume al revés. Regexp.timeout no protege contra estos bugs. El timeout existe para limitar el tiempo de ejecución de un match, no el daño que hace la fase de compilación, que es donde ocurre la corrupción de memoria. Y limitar la longitud de los patrones tampoco protege de forma fiable, porque algunos de los fallos se disparan con patrones cortos.

La recomendación oficial para quien no puede actualizar es directa: evitar por completo compilar expresiones regulares a partir de input no confiable. O sea, el trabajo de la aplicación es la defensa real aquí, y el upgrade cierra el resto.

Y hay un matiz de política que conviene tener presente para no malinterpretar los escáneres. Ruby tiene una posición de fondo: compilar y usar una regex creada a partir de input no confiable se considera inseguro, y eso está escrito en la documentación. Aun así, en casos anteriores (CVE-2022-28738 y CVE-2024-27282) Ruby sí trató los double free en compilación como vulnerabilidades y pidió identificadores propios. Con estos cuatro, en cambio, no los pidió, y por eso los números que vas a ver en tus reportes de seguridad son los de GitLab. Si tu escáner solo busca CVEs asignados a Ruby, no te va a marcar nada: busca los cuatro identificadores de GitLab explícitamente.

Vale la pena actualizar hoy

Mi lectura, para un lector que administra servidores Linux: actualiza esta semana, y no porque los cuatro CVE te afecten a ti, sino por dos razones que valen más que el CVE en sí.

La primera es práctica. Ruby 4.0.8 es un señuelo: se publicó el 22 de octubre, un día antes del aviso, y mucha gente va a ver “la última 4.0” y actualizar a ella. Un día de diferencia entre 4.0.8 y 4.0.9 y un CVE de 10.0 sin autenticación de diferencia. La segunda es de higiene operativa: si tu versión de Ruby está en una serie que ya no recibe fixes de seguridad (3.2 y anteriores, con EOL el 1 de abril de 2026), cada día que pasa es un día más sin cobertura para todo lo demás que salga. Saltar de 3.2 a 3.4 o 4.0 no es una operación de un minuto, es el momento de planificarlo bien.

Para el que tiene Ruby en un homelab, el orden que sugiero es este: primero el diagnóstico de versión, después el grep en el código para saber si hay superficie expuesta, y solo entonces el upgrade. Si el grep no devuelve nada, tu prioridad real es la higiene de la plataforma, no el CVE: actualiza en la próxima ventana de mantenimiento, sin dramas y sin saltarte respaldos.

Preguntas frecuentes

¿Mi aplicación Ruby está expuesta aunque nunca haya usado GitLab?

Depende de una sola cosa: si compila una expresión regular a partir de un string que el atacante controla. Si tus regex están escritas en el código por tus developers, no hay exposición. Si en algún punto haces Regexp.new, Regexp.union, Regexp.compile o interpolas input dentro de un literal /#{input}/, ahí sí hay que actualizar y además corregir ese camino.

¿Cuál es la versión mínima de Ruby que tengo que instalar?

Ruby 4.0.9, 3.4.12 o 3.3.13, o cualquier posterior de esas series. La trampa más común es quedarse en 4.0.8, que no incluye el fix de CVE-2026-110264. Y las series 3.4 y 3.3 solo quedaron cubiertas a partir de .12 y .13 respectivamente: antes de eso no hay ninguno de los tres parches.

¿Qué hago si no puedo actualizar Ruby ahora mismo?

La recomendación oficial es evitar por completo compilar regex a partir de input no confiable: valida el patrón antes de pasarlo a Regexp.new, o escapa el valor con Regexp.escape si en realidad es texto literal. Ten en cuenta que Regexp.timeout y limitar la longitud del patrón no sirven aquí, porque el fallo ocurre al compilar, no al ejecutar el match.

¿Por qué los CVE no aparecen asignados a Ruby?

Porque Ruby decidió no solicitar identificadores CVE para estos fixes, por ahora. GitLab asignó los cuatro y los publicó en NVD con sourceIdentifier [email protected], y Ruby pidió usar esos identificadores en los escáneres. Si tu herramienta de seguridad solo cruza CVEs del proyecto Ruby, esos cuatro no van a aparecer: busca los identificadores de GitLab de forma explícita.

¿Tengo que actualizar GitLab además de Ruby?

Sí, si eres el administrador de una instancia self-hosted. El fix de Ruby cierra el bug de fondo, pero los CVE publicados son de GitLab y el parche de producto está en sus releases: 19.4.1 y posteriores cubren los dos primeros, 19.4.2 el tercero y 19.4.3 el que arrastra el alcance desde GitLab 7.0. Verifica la ruta de upgrade de tu rama antes de fijar un número de versión.

Lee también

Alfonso Moreno | Sysadmin Senior

Soy Alfonso, sysadmin senior con RHCE y más de 10 años administrando sistemas Linux para empresas y proyectos personales. Me especializo en infraestructura, redes, virtualización, monitoring, automation y self-hosting. Sobre SoyLinux.com SoyLinux.com es un blog técnico en español sobre Linux, software libre y self-hosting. Lo mantengo en mi tiempo libre. Áreas de expertise - Linux server administration: RHEL, CentOS, Rocky, Alma, Fedora, Debian, Ubuntu - Network services: Postfix, BIND, Samba, NFS, Squid - Virtualización y containers: KVM, Proxmox, Docker, Podman - Monitoring: Prometheus, Grafana, Zabbix - Automation: Ansible, Bash scripting - Self-hosting: Vaultwarden, Nextcloud, Jellyfin, Immich, Homelab Contacto - Email: [email protected]

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *