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
-
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 -
Read it before running it. Always.
less ruby-regexp-oct2026-check.sh -
Make it executable.
chmod +x ruby-regexp-oct2026-check.sh -
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 -
Find the code that compiles patterns from strings. Point
SCAN_PATHSat your application roots if they live somewhere else.sudo SCAN_PATHS="/srv /var/www" ./ruby-regexp-oct2026-check.sh scan -
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
- GitLab. Omnibus installs under
/opt/gitlab, source installs under/home/git/gitlab, andgitlab/gitlab-ceandgitlab/gitlab-eeimages in Docker or Podman. The version comes from GitLab's ownVERSIONfile or version manifest, or from the image tag. An image taggedlatestcan't be judged and is reported as unknown. - Discourse. The Docker launcher under
/var/discourse, a bare-metal checkout under/var/www/discourse(or a user home, or/srv/discourse), anddiscourse/discourseanddiscourse/baseimages. 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 Discourselibheifimage-upload RCE, which is fixed by rebuilding the container, not by upgrading Ruby. - 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. - 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. - Running processes. Every process whose binary is a Ruby or that has
librubyloaded, which catches Puma, Sidekiq and Unicorn under their own names, with its user and systemd unit. A process whose Ruby binary orlibrubywas 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:
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.- Changelog. For a Ruby installed from a Debian or Ubuntu package, the changelog of the interpreter package or of its
librubypackage 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 toFIXED_BUILDS. - 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 faultblocks Ruby prints when it dies; - Onigmo and Regexp frames (
onig_compile,rb_reg_initialize,regparse.cand 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,SIGABRTorSIGBUS; 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.timeoutand 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.9or later. The script checks GitLab images by tag but can't see Rubies inside other images. scanis 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.