Skip to content
RY

The Ruby Bug Behind GitLab’s CVEs, Explained Like You’re Not a Programmer

Ram Laxman Yadav10 min read

On 23 October 2026, the Ruby programming language team published a fix for a problem that sounds extremely technical — “regexp compilation fixes” — but is actually quietly tied to four real security bugs that GitLab had to patch: CVE-2026-89078, CVE-2026-93577, CVE-2026-97229, and CVE-2026-110264.

If words like “regexp compilation,” “double free,” and “integer overflow” mean nothing to you, don’t worry — by the end of this post they will. No prior programming knowledge needed. We’ll build everything up from scratch, with one simple example you can actually picture in your head.

First, what even is a “regex”?

A regex (short for “regular expression”) is just a tiny pattern you give to a computer so it can find or check text. Think of it like a very precise version of the search box in a document — except instead of just searching for the word “cat”, you can search for shapes of text.

For example, a regex can say: “find something that looks like a phone number — three digits, a dash, three digits, a dash, four digits.” In the Ruby programming language, that looks like this:

phone_pattern = /\d{3}-\d{3}-\d{4}/
"Call me at 123-456-7890" =~ phone_pattern
# => matched! Ruby found a phone-number-shaped piece of text

You don’t need to understand that symbol soup. Just know this: a regex is a rulebook that the computer reads and then uses to scan text. Reading that rulebook and turning it into something the computer can actually run is called compiling the regex. That’s the exact step where all four of these bugs live.

So what actually went wrong?

Here’s the key idea, in one sentence:

Ruby’s engine for reading regex rulebooks had bugs that let a very unusual, specially-crafted rulebook confuse Ruby’s memory management — and GitLab happened to let people feed it rulebooks it shouldn’t have trusted.

Let’s unpack the two kinds of bugs in plain language, because both sound scary but are easy to picture.

Bug type 1: “Double free” — like returning a library book twice

Imagine a library system where, after you return a book, the system marks that book’s shelf slot as “empty, ready for the next book.” Now imagine a glitch where the system accidentally marks the same slot as empty twice — once correctly, and once by mistake. If another book gets placed there in between, the second “this slot is empty” message can wipe out or scramble that new book’s record.

That’s basically what a double free is in programming. When a program is done using a piece of computer memory, it tells the computer “I’m done, this memory is free for something else to use.” A double free happens when the program accidentally says that twice for the same piece of memory — and in between those two messages, something else may have already moved into that memory. The result is scrambled, corrupted data that the program then tries to use as if it were still valid. This is what CVE-2026-89078 was about: a regex rulebook that failed to parse correctly (specifically, one using an “alternation” — the regex equivalent of saying “match THIS or THAT”) could trigger exactly this kind of double free.

Bug type 2: “Integer overflow” — like an odometer rolling over

Picture a car’s odometer that only has six digits. If you drive far enough, it goes from 999999 back to 000000 — it “wraps around” because it ran out of room to count any higher. If anything in the car relied on that number only ever going up, that wrap-around would break it.

Computers do the same thing with numbers internally. A program often reserves a fixed amount of space to store a count — for example, “how big is this compiled pattern” or “how many internal pieces does this pattern have.” If a regex rulebook is unusual enough (for instance, it repeats a tiny piece of itself an enormous number of times), that internal counter can overflow and wrap around to a small or nonsensical number. The program then uses that wrong number to decide how much memory to set aside — and ends up writing data into memory it never actually reserved. That’s CVE-2026-93577, CVE-2026-97229, and CVE-2026-110264 in a nutshell — each one is a different spot in Ruby’s regex engine where a counter could overflow like that.

This is NOT the 'slow regex' bug you may have heard of

If you’ve heard of ReDoS (Regular Expression Denial of Service), you might assume this is that — a regex that takes forever to run and freezes the server. It isn’t. These four bugs are about memory corruption while Ruby is still just reading and compiling the pattern, before it even runs against any text. That’s a more serious category of bug, because memory corruption can potentially be steered into running an attacker’s own code, not just slowing things down.

Why does this matter specifically for GitLab?

By itself, a bug in how Ruby reads regex patterns isn’t automatically dangerous. If you, the developer, write every regex pattern in your own code, an attacker has no way to hand Ruby a “booby-trapped” pattern — your patterns are fixed and never written by strangers.

The danger shows up when a program takes text typed in by a random user and turns it directly into a regex pattern. GitLab does this in a few places — most notably in CI/CD configuration, where you can define rules using pattern matching. Under the hood, GitLab was handing that user-supplied text to Ruby’s Regexp.new, which compiles it into a real, runnable pattern.

Put those two things together, and here’s the real-world chain of events GitLab had to worry about:

  1. An attacker finds a spot where GitLab lets them type something that becomes a regex pattern (for example, a CI/CD rule).
  2. Instead of a normal pattern, they type a deliberately weird one designed to trigger a double free or integer overflow.
  3. GitLab passes it to Ruby to compile.
  4. Ruby’s regex engine gets confused, memory gets corrupted, and — in the worst case — that corruption can be shaped into running code the attacker chose, on GitLab’s own server.

That last part is why GitLab’s advisories flagged these as potential remote code execution (RCE) issues — not “the website shows an error,” but “someone else’s computer, running GitLab, could end up doing what the attacker wants.”

A simple code example

Here’s the same idea boiled down to a tiny, easy-to-read example. Imagine two different ways a web application might use a regex:

# SAFE — the developer wrote this pattern themselves.
# It never changes, and no user ever gets to influence it.
email_pattern = /\A[\w+\-.]+@[a-z\d\-]+\.[a-z]+\z/i
if user_email =~ email_pattern
puts "Looks like a valid email!"
end
# RISKY — the pattern itself comes from whatever a user typed in,
# for example a "custom filter" box in a settings page.
user_supplied_pattern = params[:filter] # attacker-controlled text
dynamic_regex = Regexp.new(user_supplied_pattern) # Ruby must "compile" it
if some_text =~ dynamic_regex
puts "Match found"
end

The first example is fine, no matter which Ruby version you’re on — the pattern is fixed, written by a developer, and never touched by outside input. The second example is exactly the kind of code path these CVEs are about: Regexp.new (and similarly Regexp.union, or writing something like /#{user_input}/) hands Ruby’s compiler a pattern it didn’t choose and can’t fully trust. On a vulnerable Ruby version, a cleverly broken user_supplied_pattern is the “booby-trapped rulebook” from earlier.

The four bugs, at a glance

GitLab’s name for it The real underlying Ruby bug Fixed in Ruby
CVE-2026-89078 — Double Free in Regex Parser Memory gets marked “free” twice after a broken pattern using alternation (this|that) 4.0.0, 3.4.12, 3.3.13
CVE-2026-93577 — Integer Overflow in Regex Compiler A size counter for the compiled pattern wraps around 4.0.8, 3.4.12, 3.3.13
CVE-2026-97229 — Integer Overflow in CI/CD Regex Compiler Same overflow bug as above, hit through GitLab’s CI/CD pattern matching 4.0.8, 3.4.12, 3.3.13
CVE-2026-110264 — Integer Overflow in Regex Compiler An internal ID counter overflows on patterns with a huge number of repeats 4.0.9, 3.4.12, 3.3.13

One of these (the overflow fixed in Ruby pull request #19020) was actually found and reported by security researcher Johan Carlsson through GitLab’s own bug bounty program — a good reminder that responsible disclosure is exactly how bugs like this are supposed to surface.

Is this actually a problem for you?

Ask yourself one simple question:

“Does any part of my application ever turn text typed by a user into a regex pattern?”

  • If no — all of your regex patterns are things you, the developer, typed directly into your code, and no user input ever becomes part of a pattern — then you’re in the lowest-risk group. It’s still smart to upgrade Ruby, but these specific bugs need an attacker-controlled pattern, not just attacker-controlled text being searched.
  • If yes — your app uses something like Regexp.new(some_user_input), Regexp.union(user_values), or builds a pattern with string interpolation like /#{user_input}/ — then you should treat this as something to fix promptly, exactly the way GitLab did.
  • If you run GitLab yourself (self-managed, not GitLab.com), this applies to you directly through the CI/CD regex compiler path, regardless of whether your own code does anything with regexes at all.

Two tempting 'fixes' that do NOT actually protect you

You might think: “I’ll just set a timeout on my regex, or limit how long a pattern can be, and I’ll be safe.” Unfortunately, neither helps here. Regexp.timeout only protects against patterns that run slowly (the ReDoS problem mentioned earlier) — it does nothing for memory corruption that happens while the pattern is being compiled, before it ever runs. And limiting pattern length doesn’t reliably help either, since some of these bugs can be triggered with short patterns. The only real fix is upgrading Ruby.

How to actually fix it

Step 1 — Check your current Ruby version.

Terminal window
ruby -v

Step 2 — Compare it against the patched versions. You want to be on one of these, or newer:

  • Ruby 4.0.9
  • Ruby 3.4.12
  • Ruby 3.3.13

Step 3 — Upgrade. However you normally manage your Ruby version — rbenv, rvm, asdf, or your OS package manager — install the patched version and make sure your app (and anything running it, like Docker images or CI runners) actually switches to it.

Terminal window
# example with rbenv
rbenv install 3.3.13
rbenv local 3.3.13

Step 4 — If you run GitLab specifically, make sure you’re on a patched release: 19.4.1, 19.4.2, or 19.5.1, or anything newer — these ship with the fixed Ruby already bundled in.

Can't upgrade Ruby right away?

If you’re stuck on an older Ruby for now, the one real mitigation is to stop compiling regex patterns from untrusted input entirely — validate user input against a fixed, developer-written pattern instead of turning their input into the pattern. It’s not a complete substitute for upgrading, but it closes off the specific path these bugs need.

The takeaway, in three lines

  • Four GitLab CVEs trace back to real bugs inside Ruby’s own regex engine — not GitLab’s application logic.
  • The danger only appears when a program compiles a regex pattern that came from a user, rather than from a developer’s own code — GitLab’s CI/CD pattern matching was one such spot.
  • Upgrade to Ruby 4.0.9, 3.4.12, or 3.3.13 (or newer) — a timeout or a pattern-length limit will not protect you here.

References: the official Ruby announcement, CVE records for CVE-2026-89078, CVE-2026-93577, CVE-2026-97229, and CVE-2026-110264, GitLab’s patch release notes for 19.4.1, 19.4.2, and 19.5.1, and the underlying Ruby fixes in pull requests #13332, #19020, and #16801.