Operating system, libraries and runtimes

Ruby and GitLab: Regexp CVEs

Ruby and GitLab Regexp Check

On 23 October 2026 Ruby released 4.0.9, 3.4.12 and 3.3.13, which fix memory corruption bugs in Onigmo, the engine behind Ruby's regular expressions. GitLab showed that a crafted pattern can turn them into remote code execution on a GitLab server, and published four CVEs: CVE-2026-89078, CVE-2026-93577, CVE-2026-97229 and CVE-2026-110264. Two of them, CVE-2026-97229 and CVE-2026-110264, need no login, and CVE-2026-110264 affects every GitLab version from 7.0.

A Ruby application is exposed if it compiles a pattern an attacker controls, through Regexp.new, Regexp.compile, Regexp.union or a literal such as /#{input}/. Patterns your own developers wrote are not affected. Ruby requested no CVEs of its own, so a scanner that follows Ruby CVEs shows nothing: follow the GitLab CVEs instead.

Regexp.timeout and length limits do not protect against these bugs, so the fix is to upgrade Ruby and GitLab. check finds what needs upgrading, scan finds applications that compile patterns from strings, and hunt looks for crashes that look like exploitation.

Ruby quick start

  1. Download the script. The repository is mirrored on GitHub and Codeberg; pick a base URL.

    # BASE from GitHub:   https://raw.githubusercontent.com/PrivacySafe/security-patches/refs/heads/main
    # BASE from Codeberg: https://codeberg.org/PrivacySafe/security-patches/raw/branch/main
    # curl -fLO "$BASE/ruby/ruby-regexp-oct2026-check.sh"
    curl -fLO https://raw.githubusercontent.com/PrivacySafe/security-patches/refs/heads/main/ruby/ruby-regexp-oct2026-check.sh
    
  2. Read it before running it. Always.

    less ruby-regexp-oct2026-check.sh
    
  3. Make it executable.

    chmod +x ruby-regexp-oct2026-check.sh
    
  4. See what's exposed. Root is optional, but without it you won't see other users' Rubies and processes.

    sudo ./ruby-regexp-oct2026-check.sh check
    
  5. Find the code that compiles patterns from strings. Point SCAN_PATHS at your application roots if they live somewhere else.

    sudo SCAN_PATHS="/srv /var/www" ./ruby-regexp-oct2026-check.sh scan
    
  6. Look for crashes that suggest someone already tried.

    sudo ./ruby-regexp-oct2026-check.sh hunt
    

To confirm GitHub and Codeberg serve the same file, compare checksums:

sha256sum ruby-regexp-oct2026-check.sh
curl -fsSL https://codeberg.org/PrivacySafe/security-patches/raw/branch/main/ruby/ruby-regexp-oct2026-check.sh | sha256sum

If you run GitLab, upgrade it first. The fixed releases are 18.11.13, 19.0.10, 19.1.9, 19.2.9, 19.3.6, 19.4.3 and 19.5.1. GitLab patched the Ruby it ships, so upgrading the system ruby package does nothing for a GitLab install, and upgrading GitLab is the whole fix for it.

Ruby commands

Command What it does
check (default) Reports every GitLab and Discourse install, every Ruby runtime on disk and every running Ruby process, with whether each is fixed. Flags processes still running a Ruby that was replaced on disk since they started. Changes nothing, root optional. Exits 0 if nothing running is exposed, 1 if unfixed Ruby is running or a bundled app is unfixed, 2 if some version couldn't be read
scan Read-only. Finds application code that compiles regexps from strings, grouped by application, and grades each line (see Ruby scan grades). Exits 0 if nothing needs review, 1 if something does, 2 if none of the scan paths exist (nothing was scanned)
hunt Read-only log triage. Scans the journal, syslog, kernel log, GitLab's Puma, Sidekiq and Rails logs, and application logs under /srv and /var/www, for Ruby crash reports, Onigmo frames in backtraces, heap corruption aborts, services killed by a crash signal, and RegexpError. Exits 0 if nothing matched, 1 if it found something to look at, 2 if no journal or log could be read (nothing was searched)
version Prints the script version. Every other command prints it on its first line too

What the Ruby check looks at

  1. GitLab. Omnibus installs under /opt/gitlab, source installs under /home/git/gitlab, and gitlab/gitlab-ce and gitlab/gitlab-ee images in Docker or Podman. The version comes from GitLab's own VERSION file or version manifest, or from the image tag. An image tagged latest can't be judged and is reported as unknown.
  2. Discourse. The Docker launcher under /var/discourse, a bare-metal checkout under /var/www/discourse (or a user home, or /srv/discourse), and discourse/discourse and discourse/base images. Discourse has published no Discourse-version fix for these CVEs, so each install is judged by the Ruby it runs: a bare-metal checkout by its .ruby-version, the Docker launcher by the running web process, since that Ruby lives inside the container. Discourse compiles patterns from admin and user input (watched words, search, theme and automation settings), so an unfixed Ruby is reachable. This is the Ruby Regexp issue, separate from the Discourse libheif image-upload RCE, which is fixed by rebuilding the container, not by upgrading Ruby.
  3. Ruby runtimes on disk. Everything on PATH, /usr/bin/ruby*, /usr/local/bin/ruby, chruby's /opt/rubies, and every user's rbenv, rvm, asdf, mise and chruby installs.
  4. Vendor-bundled Rubies. Rubies that ship inside another product, under /opt/*/embedded (Chef, Fluentd, Vagrant and others) or /opt/puppetlabs, are listed but not judged by version: their fix comes from that vendor's update. GitLab's embedded Ruby is judged by the GitLab version, since GitLab patched it.
  5. Running processes. Every process whose binary is a Ruby or that has libruby loaded, which catches Puma, Sidekiq and Unicorn under their own names, with its user and systemd unit. A process whose Ruby binary or libruby was replaced on disk since it started still runs the old code until you restart it.

The script reads each Ruby version from the description string compiled into the binary or into the libruby it links. It never executes a Ruby binary, so root never runs one planted in a user's version manager.

Ruby patch detection

For each runtime, three checks run in order, and the first that applies decides:

  1. FIXED_BUILDS. Distro builds you list as fixed, for a backport the script doesn't know about. A build only counts for the same upstream version.
  2. Changelog. For a Ruby installed from a Debian or Ubuntu package, the changelog of the interpreter package or of its libruby package names one of the four GitLab CVEs. The changelog only counts if its newest entry is the installed version. Because Ruby requested no CVEs, a distro may describe the fix without naming them, if you know your build is fixed, add it to FIXED_BUILDS.
  3. Upstream version. The runtime is at or above the fixed release for its line: 3.3.13, 3.4.12 or 4.0.9. Older lines read as not fixed: move to a fixed line. A line newer than 4.0 reads as unknown: check that the build includes ruby/ruby#13332, #19020 and #16801.

GitLab is judged the same way against its fixed release for each minor line. A minor line older than 18.11 moves to a fixed line. A line newer than 19.5 shipped after the fixes and reads as fixed.

Ruby scan grades

scan searches .rb, .rake, .erb, .haml, .slim and .ru files and skips literal patterns. What's left gets one of two grades:

Grade Meaning
review The pattern comes from a variable or expression. Trace where it comes from: if a user can set it (request parameters, CI configuration, saved filters, webhook payloads, search syntax), that application is exposed until its Ruby is fixed
escaped The dynamic part goes through Regexp.escape or Regexp.quote, or it's a Regexp.union of strings, which escapes them. A much smaller surface that is still compiled, so still worth a look

The protective fix lives in the application: stop compiling the user's text, or pass it through Regexp.escape so it is matched literally. GitLab's tree is skipped because GitLab is judged by its version. Installed gems and vendor/bundle are skipped by default because they are noisy. Set SCAN_GEMS=1 to include them.

Ruby hunting for exploitation

These bugs corrupt memory while a pattern compiles, so a failed attempt usually shows as a crashed worker rather than an application error. hunt reads the journal and logs, never changes anything, and reports:

  • Ruby crash reports, the [BUG] Segmentation fault blocks Ruby prints when it dies;
  • Onigmo and Regexp frames (onig_compile, rb_reg_initialize, regparse.c and others) in a backtrace;
  • glibc heap corruption aborts such as double free or corruption;
  • kernel segfault and protection fault lines from Ruby, Puma, Sidekiq and Unicorn processes;
  • services killed by SIGSEGV, SIGABRT or SIGBUS;
  • RegexpError, which someone probing patterns leaves behind.

HUNT_SINCE sets how far back it reads the journal, and HUNT_LOGS adds your own log files.

A match tells you exactly where to look. Native gems crash Ruby too, and RegexpError is ordinary when users type bad patterns. A crash with Onigmo frames on an unfixed Ruby, or a burst of RegexpError from one source just before a worker died, is worth treating as an incident: check what the process could reach and rotate it. On GitLab that includes runner registration tokens and CI/CD variables.

Ruby configuration

Variable Default Purpose
RUBY_FIXED_33, RUBY_FIXED_34, RUBY_FIXED_40 3.3.13, 3.4.12, 4.0.9 First fixed release for each line
FIXED_BUILDS (empty) Extra fixed distro builds, space-separated full versions
GITLAB_FIXED 18.11.13 19.0.10 19.1.9 19.2.9 19.3.6 19.4.3 19.5.1 First fixed GitLab release for each minor line
GITLAB_ROOT /opt/gitlab Where Omnibus GitLab is installed
DISCOURSE_ROOT /var/discourse Where the Discourse Docker launcher lives
DISCOURSE_APP /var/www/discourse Where a bare-metal Discourse checkout lives
SCAN_PATHS /srv /opt /var/www /home /usr/local/lib Where scan looks for code
SCAN_DEPTH 10 How deep scan goes
SCAN_GEMS 0 1 also scans installed gems and vendor/bundle
SCAN_SHOW 5 Findings printed per application and grade
HUNT_SINCE 2026-09-01 How far back hunt reads the journal
HUNT_LOGS (empty) Extra log globs for hunt

Before you run it

  • This is a check, not a mitigation. Upgrade GitLab, upgrade Ruby to 4.0.9, 3.4.12 or 3.3.13, and restart what runs on it. Ruby 3.3 normally gets only security fixes. Ruby says it included these as an exception.
  • No setting protects you. Regexp.timeout and length limits don't. Until you can upgrade, the protective fix is in the application: don't compile patterns from untrusted input.
  • GitLab is fixed by upgrading GitLab , not by upgrading the system Ruby.
  • Vendor-bundled Rubies need the vendor's update. The script lists them so you know they exist.
  • Containers get their own fix. Each image carries its own Ruby. Rebuild on ruby:3.3.13, 3.4.12, 4.0.9 or later. The script checks GitLab images by tag but can't see Rubies inside other images.
  • scan is a grep. It misses patterns built across several lines or in another file, and it can flag interpolated strings that aren't regexps. Use it to find where to look, not as a verdict.
  • GNU/Linux only. Process inspection reads /proc.

Red Hat advisories

Red Hat's advisory list (security advisories released 2026-04-01 to 2026-11-03) names the CVEs below. A Red Hat build is judged by its RPM release, not by the upstream version number, because Red Hat backports fixes: the advisory names the release that fixes it. A CVE with no advisory in the list has none for that period, which is not the same as not affected. The current state is on Red Hat's CVE database.

Advisory Released Severity Package and stream CVEs on this page
RHSA-2026:88861 2026-10-23 Critical ruby3.4-3.4.12-33.1.hum1 (Hummingbird) CVE-2026-110264, CVE-2026-97229, CVE-2026-89078, CVE-2026-93577
RHSA-2026:88865 2026-10-24 Critical ruby4.0-4.0.9-41.hum1 (Hummingbird) CVE-2026-110264, CVE-2026-97229, CVE-2026-89078, CVE-2026-93577
RHSA-2026:88880 2026-10-24 Critical ruby3.3-3.3.13-23.8.hum1 (Hummingbird) CVE-2026-110264, CVE-2026-97229, CVE-2026-89078, CVE-2026-93577

The Security Patches software and this documentation are dedicated to the public domain under CC0 1.0 Universal. Other pages on privacysafe.app/docs are CC BY-SA 4.0 unless the page says otherwise.

Edit this page on Codeberg

The PrivacySafe Ecosystem

Shop at Ivy Cyber

Ivy Cyber offers PrivacySafe products and services in the USA and has partnerships with 3NSoft in Canada and Kuip Limited in the European Union. Enterprise services and consulting are available at ivycyber.com

PrivacySafe Foundation is a 501(c)(3) nonprofit public charity that supports the development of the PrivacySafe applications, along with the components they rely upon: 3NWeb protocols, filesystem, documentation, and reference implementations.

PrivacySafe Foundation

Donations to PrivacySafe Foundation support education, research, documentation, and public code. Purchases from Ivy Cyber support PrivacySafe products, services, hosting, and technical support. Together, we're building a better web.