Back to Blog

The 10.0 carries GitLab's name and the bug is Ruby's

Between 23 September and 21 October, GitLab shipped three rounds of critical patches fixing four separate vulnerabilities. All four were found by the same person. All four live in the same place: Onigmo, the regular expression engine Ruby carries inside and that GitLab packages along with its own interpreter.

That has two practical consequences, and neither fits inside the sentence "we already patched it". The first is that every patch left the version you had just installed inside the affected range of the next advisory, three times running. The second is that the fix for the last one is not a line of GitLab code: it is a patch to the interpreter, and that changes where you have to look to find out whether it reaches you.

Four CVEs, three rounds, one engine

Everything below comes from the three patch release notes GitLab published. The researcher is credited under the handle joaxcar in all three, always through the company's HackerOne bug bounty programme.

Round What it fixes Who it reaches
23 Sep
19.4.1 / 19.3.3 / 19.2.7
CVE-2026-89078 (9,9)
CVE-2026-93577 (9,9)
Both with PR:L: an account is required.
19.2, 19.3, 19.4
12 Oct
19.4.2 / 19.3.5 / 19.2.8
CVE-2026-97229 (10,0)
Now PR:N. In KEV since the 29th.
19.2, 19.3, 19.4
21 Oct
19.5.1 / 19.4.3 / 19.3.6 / 19.2.9 / 19.1.9 / 19.0.10 / 18.11.13
CVE-2026-110264 (10,0)
PR:N. Not in KEV.
"all versions from 7.0 before 18.11.13", plus the six 19.x branches below their fix. A decade of versions.

The middle column is the one telling the story, and this reading is ours. In September both flaws required an account: the vector carried PR:L, low privileges but privileges. In October, twice, the same researcher went back to the same compiler and that requirement vanished. What the table shows is somebody systematically working through a surface nobody had explored, finding a cheaper way in each time.

The version treadmill

This is arithmetic from the official ranges. If you upgraded on 23 September you landed on 19.4.1, and the 12 October advisory lists "19.4 before 19.4.2" as affected. If you then moved to 19.4.2, the advisory of the 21st lists "19.4 before 19.4.3". Three rounds, and after each one the freshly installed version shows up on the list again. The same goes for the 19.3 and 19.2 branches, with their own numbers.

There is nothing to reproach anyone who patched quickly: they did the right thing all three times. The problem is that "it is already patched" has an expiry date and nobody writes it down anywhere. What protects you here is not reacting faster to the next headline, but having a written cadence for that specific machine: a recurring window, a named owner and a check afterwards that the window actually happened. It is the least glamorous part of IT maintenance, and it is what turns an advisory into a scheduled task.

A 16-bit field

The clearest explanation of the last flaw is in no press release. It is in the merge request with which GitLab brought the fix into its own packaging, written for whoever had to review it: "Onigmo stores range-repeat and null-check IDs in a signed 16-bit bytecode operand but counts them in an int, so a regexp with more than 32,767 of either wraps the ID negative. At match time the negative ID indexes the repeat stack, an out-of-bounds write."

Translated: Onigmo numbers a pattern's repeats internally. It counts those numbers in a wide integer but stores each one in a signed 16-bit field, so past 32,767 the identifier flips over and stays negative. Later that negative number is used as an index into a stack, and that is where it writes out of bounds. The fix rejects the pattern at compile time, with an error, instead of accepting it. Worth saying: nobody writes a regular expression with 32,768 repeats by accident. You have to write it on purpose, and you have to have somewhere to put it.

Where each fix lives

There is a distinction here worth getting right, because the two cases are not the same. GitLab closed the 12 October one on its own side: its description says the problem was "insufficient regular expression validation", and it was fixed by validating before compiling. The one from the 21st is a different animal. Its fix is a file under config/patches/ruby/ inside omnibus-gitlab, one per packaged Ruby (3.3, 3.4 and 4.0), applying a commit from Ruby itself verbatim.

The reason GitLab carries that patch is given in the merge request itself: the original fix was only on Ruby's main branch, the future 4.1, and not on the 3.3, 3.4 or 4.0 branches everyone actually runs. Ruby published its own fixed versions two days later, on 23 October: 4.0.9, 3.4.12 and 3.3.13, closing the Onigmo bugs behind all four CVEs in this story. There is a telling detail in the same merge request: in the 3.3 and 3.4 variants, the slot where the new error codes go was already taken by an earlier Ruby patch, the one for compiled program size, and in the 4.0 variant it was not. Which means this was not the first.

The operational consequence is short. If your GitLab is the Omnibus package or the official container image, the interpreter comes inside it and upgrading GitLab brings you the patch; that is most installations and it is enough. If your GitLab was installed from source against the system Ruby, upgrading GitLab does not touch that interpreter, and the boundary becomes 4.0.9, 3.4.12 or 3.3.13. And if that Ruby is packaged by your distribution, what decides is the version your distribution serves today. It is the minority case, and it is where the inventory falls short: the row says "GitLab 19.5.1" and the component that fails is a layer below.

And the rest of your Ruby applications

All four CVEs carry GitLab's name because that is where a bug bounty existed to report them to. The engine is Ruby's and it sits underneath any Rails application you are running. It is worth bounding what that means straight away, because the alarmist version is false: the condition for this to affect you is compiling a regular expression out of text that comes from outside, and most applications do not do that, because their patterns are written in the code and nobody picks them from a browser. GitLab does, by design, because its CI/CD configuration lets you write expressions.

What is useful, then, is a question that rarely appears on any list: does any of our applications compile regular expressions from text that comes from outside? A search box with wildcards, a rules filter the customer configures, a validator that accepts a pattern through a form. Your vulnerability scanner will not answer it either, because it compares package names and versions, and what fails here is the interpreter running the package. This is the third time we have written about this particular shape of blind spot: it happened with six advisories and a scanner that still says zero and, in the same Ruby world, with the library nobody chose.

What this is not

This is not the story of a negligent vendor. GitLab patched its own interpreter, published the merge request in the open with the upstream commit and the full reasoning, and handled all four reports through its bug bounty programme. We wish more vendors let you read that: most publish one line and a version number.

Nor is there public evidence that the last one is being exploited. The one CISA added on 29 October, alongside a Cisco flaw and under directive BOD 26-04, is the one from the 12th. According to figures the Shadowserver Foundation published on 2 November, of the roughly 39,000 GitLab instances it sees facing the internet each day, more than 1,000 are still missing the 12 October patch and more than 11,300 the one from the 21st. That second figure is exposure, not incidents. And it should not be explained by a single cause: the patch from the 21st is nine days younger and has no official catalogue pushing it along, on top of reaching far more versions.

And the "unauthenticated" in the October advisories carries small print. Rapid7 validated the full chain against the official GitLab CE 19.4.1 image and describes the requirement like this: "an attacker must first be able to commit a malicious CI/CD configuration file .gitlab-ci.yml, and a repository path containing UTF-8 characters to a branch in a public project." That does not change whether you have to patch. It changes how many hours you have, and the answer depends on who your instance lets create projects. Incidentally, the 19.4.1 Rapid7 reproduced it against was the fixed version from the previous round.

The conflict of interest, up front: our own CI/CD runs on a self-hosted GitLab instance, with GitOps deployments to Docker Swarm and Kubernetes. We walked this timeline like everyone else.

The row your inventory is missing

Instead of a checklist, a two-column table: what your inventory says about that machine today and what it ought to be able to answer. The first three rows are settled with one command. The last one is not, and it is the one that decides.

What it says today What it has to be able to answer
"GitLab 19.4.2" Is it on the 21 October list, or did it stop at the 12th's? The boundary is 19.5.1 / 19.4.3 / 19.3.6 / 19.2.9 / 19.1.9 / 19.0.10 / 18.11.13.
(nothing) Does the interpreter come with the GitLab package or is it the system's? A ruby -v on the box, and the boundary 4.0.9 / 3.4.12 / 3.3.13.
"Development server" What can that machine sign? Deploy keys, runner registration tokens, CI variables, access to the container registry.
(nobody) Who has a first and last name for that machine, and when is its next window?

When the answer to "where did this package come from" is "someone who no longer works here built it", the first job is reconstructing the provenance and then deciding whether that machine stays hand-built or moves to supported packaging. We do not sell that decision with a licence underneath, because we resell none, and plenty of times it ends in "leave it alone and give it a window"; that is the job of consulting. Making sure the window exists every month and that somebody checks afterwards that it happened is maintenance.

Five weeks, four vulnerabilities and a 16-bit field inside an interpreter nobody deliberately chose. The CVE score does not tell apart the team this cost an afternoon from the team it cost a night shift. What tells them apart is whether somebody had written down, before 23 September, whose machine that was and which day was its turn.

Sources. The three rounds come from the vendor's own patch release notes, consulted on 3 November 2026: 23 September (19.4.1), with CVE-2026-89078 and CVE-2026-93577, both 9.9 and carrying PR:L; 12 October (19.4.2), with CVE-2026-97229, 10.0, vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H; and 21 October (19.5.1), with CVE-2026-110264, 10.0 and the same vector. The versions, affected ranges, quoted descriptions and the credit to joaxcar come from there too. The KEV listing, the Cisco CVE added the same day and directive BOD 26-04 come from CISA's alert of 29 October 2026. The exploitation precondition and the validation against the 19.4.1 image come from Rapid7's analysis. The explanation of the overflow, the patch file and the reason for applying it to 3.3, 3.4 and 4.0 come from omnibus-gitlab merge request 9921. The instance figures are attributed to the Shadowserver Foundation by Techzine on 2 November 2026, and are a third-party measurement, not a count of ours. Technical quotes are left in their original English so they can be searched verbatim. Reading the drop from PR:L to PR:N as a surface under exploration is our own, and is flagged as such in the text. We already covered CVE-2026-97229 on 14 October from a different angle, that of rebuilding the instance, in the post we devoted to it on 14 October. The social image photograph is "Cable closet bh", public domain, via Wikimedia Commons.

Who has a first and last name for your GitLab?

We look at it with you: which version is really running, where the package came from, what that machine can sign and what monthly window it has written down. Without selling licences for anything.

Talk to everyWAN

Tags:

Share:

Subscribe to our newsletter

To receive IT stories, everyWAN news and exclusive subscriber offers, sign up to our mailing list

Minorisa de Sistemas Informaticos y Gestión S.L. © 2026
everyWAN
everyWAN