Cybersecurity & Protection

Ruby: Regex Engine Bugs Are Behind Critical GitLab Flaws

Oct 30, 2026 7 min read
All articles

The Ruby team has released versions 4.0.9, 3.4.12 and 3.3.13. They fix three bugs in the Onigmo regex engine that attackers can use to run malicious code on affected servers. The best-known case is the string of critical GitLab flaws from the past few weeks. But any Ruby application that builds regular expressions from untrusted input is at risk. If you only use patterns you wrote yourself, you are not affected, according to the Ruby team.

One detail matters here: before Ruby applies a pattern to a string, it compiles the pattern into an internal program. The bugs live in that compile step, not in the actual matching.

Four GitLab CVEs, three Ruby bugs

GitLab compiles regular expressions from user input, for example from CI/CD configurations. Security researcher Johan Carlsson reported four flaws through the bug bounty program: CVE-2026-89078 and CVE-2026-93577 with a CVSS score of 9.9, plus CVE-2026-97229 and CVE-2026-110264 with the maximum score of 10.0, exploitable without logging in. GitLab closed them between September 23 and October 21 by patching its bundled Ruby itself. The latest update covers every GitLab version since 7.0, released in 2014.

The Ruby team maps the four CVEs to three bugs:

  • CVE-2026-89078: a double free while parsing a pattern
  • CVE-2026-93577 and CVE-2026-97229: an integer overflow when calculating the size of the compiled program
  • CVE-2026-110264: an overflow of internal counters in patterns with a very large number of repetitions

None of the fixes is new. The double free has been fixed in the development branch since May 2025 and has shipped in releases since Ruby 4.0.0. The counter fix landed in April 2026, the program size fix in September 2026. Those two only made it into releases now, though. Ruby 4.0.8 from October 22 included the program size fix as part of a routine update, without any mention of security. A day later, 4.0.9 followed with the last missing fix, and the 3.4 and 3.3 branches got all three at once. Ruby 3.3 normally only receives security fixes, and 3.3.13 is explicitly an exception because of the GitLab CVEs. Ruby 3.2 and older get no updates at all.

How a search box becomes a way in

A typical example is a search in a Rails controller that filters entries in memory:

regex = Regexp.new(params[:search].to_s)results = products.select { |name| regex.match?(name) }

The parameter comes straight from the web request. Users can search for a word or use anchors like ^ and $ to match the start and end. But they also control the pattern Ruby compiles, so an attacker can hand over a crafted one. It does not even have to be applied: the memory bug already triggers in Regexp.new. The same goes for Regexp.union and for interpolated literals such as /#{params[:search]}/.

In the worst case, the attacker's code runs with the privileges of the Ruby process, meaning access to the database, secrets in the environment and the file system. A single attempt is more likely to crash the process handling the request. Repeated, that becomes a denial of service without authentication. Neither GitLab nor Ruby has reported active attacks so far.

Often a search like this does not need the regex engine at all. If you only want to find the text the user typed, just compare strings:

query = params[:search].to_sresults = products.select { |name| name.include?(query) }

Special characters are then treated as ordinary characters. If you need to embed input in a fixed pattern, escape it with Regexp.escape. That way you still decide how the pattern is built:

regex = /\A#{Regexp.escape(params[:search].to_s)}/

A timeout will not save you, only the update will

So far, the Ruby documentation recommends a timeout for untrusted patterns, because it only warns about denial of service. But Regexp.timeout only aborts a match that runs too long. These bugs hit earlier, during compilation, where the timeout does nothing. Limiting pattern length is not reliable either, according to the Ruby team, since some of the bugs can be triggered with short patterns. If you cannot update right away, stop compiling patterns from untrusted input.

With a version manager, updating is quick: install the new version, pin it in the project, rebuild the gems. With mise, two commands do it for the 3.4 series:

mise use [email protected] install

With rbenv it takes three: rbenv install 3.4.12, rbenv local 3.4.12 and bundle install. If you pin the version in your Gemfile, update the ruby line there. Docker images get a new tag, distribution packages follow with their own delay, and bundles like GitLab Omnibus ship their Ruby with the product update. Run ruby -v to check that the server really uses the new version.

No Ruby CVEs, no scanner alerts

The Ruby project has not requested its own CVE IDs so far. Behind that is a question of principle: Ruby has always considered compiling untrusted patterns unsafe, something an application simply should not do. From that point of view, the vulnerability lies in the application, not the interpreter. For two similar bugs, CVE-2022-28738 and CVE-2024-27282, Ruby did assign CVEs after review, and the documentation still does not say clearly whether the use case is supported. The team now wants to settle that and then decide on CVEs.

For you as an operator, this has a very practical consequence. The GitLab CVEs describe GitLab versions, not Ruby versions. Vulnerability scanners that match installed software against CVE databases will therefore find nothing for an old Ruby 3.4.11 and stay silent. The only way to know you are affected is the Ruby advisory. Until a decision is made, the team recommends using the GitLab IDs for tracking.