Patched GitLab, still exposed: the CI regex bug CISA lists as exploited, and the Ruby step people miss
A crafted regex in a CI file can run code on a self-managed GitLab server with no login. Here are the fixed versions, the Ruby step that self-built installs miss, and what to rotate if you were exposed.
part 17 of 25 · Platform releases, explained from scratchthe ideaOne bad pattern in a CI file can hand over the whole server.
after reading, you can- Work out which GitLab build you need so this bug and its three siblings are all closed
- Spot a self-built GitLab that still runs an unpatched Ruby after the upgrade
- Decide what to hunt for and which secrets to rotate if the server sat unpatched
GitLab fixed a critical flaw in how it reads CI pipeline files on October 12, and CISA now lists it as exploited. Anyone running their own GitLab server should be on 19.4.2, 19.3.5 or 19.2.8 at the very least, and really on the October 29 builds, because three related bugs sit next to this one. If you built GitLab yourself or run it in your own container image, the upgrade alone can leave you exposed. Read the section on Ruby before you close the ticket.
One booby-trapped pipeline file can hand a stranger your self-hosted GitLab server and the keys stored on it.
If your company hosts its own GitLab, that server probably holds your code and runs the jobs that build and ship it. The jobs are set up in a small text file, which GitLab reads whenever someone saves or checks it. Put a certain kind of search pattern in that file and the Ruby code GitLab relies on builds something so big that its own arithmetic wraps around. When the pattern runs, Ruby jumps outside the pattern's own code into memory the attacker has shaped, and a skilled attacker can use that to run their own commands. GitLab rates it 10 out of 10. Deploy passwords, cloud keys and private repos usually sit on the same box, so the damage spreads well beyond GitLab.
Think of a robot that works through a numbered list of instructions, where some steps say 'jump back so many lines'. Someone hands it a list so long that one jump number rolls over and points somewhere off the page. The robot doesn't notice, and follows whatever is written there, which the attacker wrote.
- Anyone running GitLab on their own servers
- Teams that build their own GitLab image
- Companies whose deploy pipelines hold cloud keys
- Terraform teams running on GitLab CI
- Federal agencies and their contractors
- Other Ruby apps fed user patterns
Why it matters to you
A self-managed GitLab server is rarely just a code store. It holds the CI/CD variablesdefinitionValues GitLab stores and passes to pipeline jobs, often passwords, tokens and cloud keys. Whoever controls the server can read them. your pipelines read, which often means cloud access keys, deploy tokens and database passwords. It knows every runner and can hand jobs to them. Whoever controls that server can usually reach production through the same doors your deploy jobs use.
CISA added the flaw to its Known Exploited Vulnerabilities catalog on October 29 and marked it for forensic triage. That second flag matters for anyone doing incident response: CISA expects affected agencies to look for signs of compromise, and patching alone doesn't settle the question. Its note also warns that other products could be hit by the same underlying Ruby bug.
We do have one public account of an attack. Alpine Linux, the Linux distribution, wrote up an attempt against its own GitLab on October 20 and 21, about a week after the fix came out. A newly registered account sent crafted CI configurations, crashed GitLab's worker processes eight times and got partway through the exploit in four of them. Alpine found no evidence that code ran, couldn't rule it out, and rebuilt the whole instance from scratch.
-
01 A trapped pipeline file
Someone sends GitLab a CI configuration with a deliberately strange search pattern in it, for example through the CI lint check.
-
02 GitLab asks Ruby to compile it
GitLab passes the pattern to Onigmo, Ruby's regex engine, which compiles it into a small program of its own.
-
03 A jump wraps around
A huge repeat count makes that program so big, over 2 GiB, that an internal jump distance wraps around and turns negative.
-
04 Ruby jumps out of bounds
When the pattern runs, Ruby follows the bad jump outside its own code. Usually the worker crashes. A tuned exploit runs code instead.
-
05 The attacker owns GitLab
From here the attacker's code runs with GitLab's own rights, so repositories, CI/CD variables, runner tokens and saved credentials are all in reach.
For engineers: the weakness is CWE-190, an integer overflow. Ruby calls it an overflow in the size of a compiled pattern, and GitLab's detection notes add that a program over INT_MAX wraps a signed 32-bit jump offset negative, so at match time the regex VM jumps out of its compiled buffer into heap memory the attacker has shaped. Ruby's notice says the bugs are reached only when an application compiles a regular expression from a string an attacker controls, through Regexp.new, Regexp.union, Regexp.compile or interpolation such as /#{input}/. GitLab does exactly that with patterns in CI configuration. Ruby also says Regexp.timeout doesn't help here, and neither does capping pattern length, since some of these bugs fire on short patterns.
What GitLab fixed, and why one upgrade may not be enough
This is the third patch in a row for the same family of bugs, and the order matters. In September GitLab fixed two regex bugs that needed a logged-in user. The fix for one of them in 19.4.1 turned out to be incomplete, and CVE-2026-97229 is the way around it, with no login needed. Then on October 21 GitLab fixed a fourth bug, CVE-2026-110264, which also needs no login and reaches back to GitLab 7.0.
The four regex CVEs and where each is fixed
Needs a logged-in user, CVSS 9.9. Fixed in 19.4.1, 19.3.3 and 19.2.7 on September 23.
Needs a logged-in user, CVSS 9.9. Fixed in the same September release, though GitLab's own detection notes call the 19.4.1 fix incomplete.
CVSS 10.0, now on CISA's list. Fixed in 19.4.2, 19.3.5 and 19.2.8 on October 12. GitLab says 19.2 through 19.4 are affected.
CVSS 10.0. Fixed in 19.5.1, 19.4.3 and 19.3.6 on October 21, with backports to 19.2.9, 19.1.9, 19.0.10 and 18.11.13.
Ruby's own releases of October 23 carry the fixes for all four bugs. They matter if your GitLab doesn't use the Ruby that GitLab ships.
Released October 29 for an unrelated certificate issue. These are the newest builds and include everything above, so aim for them.
| Your GitLab line | Closes CVE-2026-97229 | Closes all four | Newest (October 29) |
|---|---|---|---|
| 19.5 | Not affected | 19.5.1 | 19.5.3 |
| 19.4 | 19.4.2 | 19.4.3 | 19.4.5 |
| 19.3 | 19.3.5 | 19.3.6 | 19.3.7 |
| 19.2 | 19.2.8 | 19.2.9 | 19.2.10 |
| 19.1 | Not affected | 19.1.9 | 19.1.10 |
| 19.0 | Not affected | 19.0.10 | 19.0.11 |
| 18.11 | Not affected | 18.11.13 | 18.11.14 |
| Older than 18.11 | Not affected | No backport, upgrade | Upgrade to a supported line |
| Your own build or image | GitLab version above, plus Ruby 3.3.13, 3.4.12 or 4.0.9 | Same | Same |
Read the 'not affected' cells with care. They refer to CVE-2026-97229 only. CVE-2026-110264 reaches every line back to 7.0, so an 18.11 server that skipped October is still open to a no-login bug rated 10.0.
From the first regex fix to the KEV deadline
- Sep 23, 202619.4.1 fixes two regex bugs that need a login
- Oct 12, 202619.4.2, 19.3.5 and 19.2.8 fix CVE-2026-97229
- Oct 20-21, 2026Attack attempt on Alpine Linux's GitLab, still on 19.3.4
- Oct 21, 202619.5.1 fixes CVE-2026-110264, backported to 18.11.13
- Oct 23, 2026Ruby 3.3.13, 3.4.12 and 4.0.9 carry all four fixes
- Oct 29, 2026CISA adds CVE-2026-97229 to KEV; GitLab releases 19.5.3
- Nov 1, 2026Federal due date
US federal agencies must have it fixed and triaged by this date. Everyone else should treat it as a sensible deadline too.
Here's the trap. GitLab's fix lives in the copy of Ruby it bundles in its Linux package and official container images. Its release notes list the change as a Ruby regexp patch for Ruby 3.3 and 3.4, applied to the omnibus-gitlab and cloud-native image builds. If your GitLab runs on a Ruby you installed yourself, the new GitLab code arrives without the fix.
Alpine found this out the hard way. It upgraded to GitLab 19.3.6 on October 22, and its post says that didn't fix the vulnerability, because its image ran standard Ruby. Someone noticed the server was still vulnerable on October 26, and Alpine rebuilt it on 19.3.7 with a fixed Ruby. Earlier, on October 13, it had moved to 19.3.4 by mistake when 19.3.5 was the one with the fix. Anyone can make either slip in a busy week.
- Uses whatever Ruby your base image or OS provides
- The GitLab upgrade brings no Ruby patch with it
- Version scanners mark it fixed when it isn't
- You still have to move Ruby to 3.3.13, 3.4.12 or 4.0.9 yourself
- Ships its own Ruby with GitLab's regex patches applied
- The next GitLab upgrade brings the patched Ruby along
- The GitLab version is a fair test of exposure
- Still needs a restart and a version check afterwards
What to do now
What to do now, by who you are
- Ask whether you run GitLab yourselves. If you only use GitLab.com, GitLab has already patched it.
- If you do run it, ask for the version and the date it was upgraded, in writing.
- Ask whether CI/CD variables hold cloud keys or production passwords. That decides how big a breach would be.
- If you sell to US federal agencies, expect questions about November 1 and have the answer ready.
- Upgrade to the newest build on your line from the table, ideally 19.5.3, 19.4.5, 19.3.7 or 19.2.10.
- If GitLab runs on your own Ruby, upgrade Ruby to 3.3.13, 3.4.12 or 4.0.9 in the same change.
- Until you're patched, take the server off the open internet or put it behind your VPN.
- Turn on admin approval for new sign-ups, and look at accounts created since October 12.
- Go through the CI/CD variables and find the ones holding cloud keys, deploy tokens or passwords. Start with Terraform pipelines.
- If the server was exposed and unpatched, rotate those secrets and the runner tokens.
- Upgrade self-hosted GitLab Runner to 19.5.1 and rebuild job images that carry their own CA bundle.
- Check other Ruby apps for patterns built from user input and move them to the fixed Ruby.
- Run GitLab's six published detections for this CVE against logs from October 12 onward.
- Look for worker crashes that mention Onigmo, and bursts of failed CI lint calls from scripted clients.
- Treat a match as a lead to investigate. A clean result doesn't prove nothing happened.
- Keep GitLab and proxy logs longer than 14 days from now on.
If you're short on time, check the version first and the Ruby second. A Linux package install reports one version, and that's the one that counts. A self-built server needs both answers, because the GitLab version tells you nothing about the Ruby underneath it.
# example output, values made up. First a Linux package server, then a self-built image sudo gitlab-rake gitlab:env:info # Linux package Ruby Version: 3.3.x Version: 19.3.4-ee # 19.3.4 is older than 19.3.5, so this box is still open. Go to 19.3.7 docker exec gitlab ruby -v # on your own image, this number decides it ruby 3.3.12 # GitLab may say 19.3.6, but this Ruby has no fix. Rebuild on 3.3.13 or later sudo grep -c 'ciLint' /var/log/gitlab/gitlab-rails/graphql_json.log # a big jump in lint calls from one user is worth reading sudo grep -l 'onig_compile' /var/log/gitlab/puma/puma_stderr.log* # crashes inside Ruby's regex compiler /var/log/gitlab/puma/puma_stderr.log.1 # A native crash through Onigmo is worth a closer look. Pull the full report before logs rotate
GitLab's detection notes explain what each log can and can't show. The GraphQL log records the CI lint request's variables in full, so a hostile pattern sent as a variable shows up there as written, provided the request finished. The REST API log filters the CI content, so all you get is a burst of server errors on the lint endpoint from a scripted client. GitLab says its GraphQL rule catches probes rather than the detonation, and its Workhorse crash rule warns that a successful exploit can leave no 502 error at all.
A patch order for this weekend
- 01Find every GitLab
Don't forget the test server, or the one a team set up for a side project, or the GitLab Runner hosts. Write down how each one was installed.
- 02Cut exposure first
Put unpatched servers behind the VPN or block them at the firewall while you schedule the upgrade. Turn off open sign-up.
- 03Upgrade to the newest build
Go straight to the October 29 build on your line, so all four regex bugs and the certificate fix are covered in one window.
- 04Fix Ruby if you own it
Self-built servers and custom images move to Ruby 3.3.13, 3.4.12 or 4.0.9 in the same change. Restart everything and check the version again.
- 05Hunt, then decide on rotation
Run the detections back to October 12. If the server was reachable and unpatched, rotate CI secrets and runner tokens even if the logs look clean.
Harden so the next one hurts less
GitLab has now fixed regex bugs in three releases since September. It compiles patterns that users write, and Ruby's notice says Ruby has generally considered compiling untrusted patterns unsafe. Expect more of these. Set the server up so the next one costs you a short patch window instead of a rebuild.
Before: GitLab holds the keys to everything
The server is on the internet with open sign-up. Long-lived cloud keys sit in CI/CD variables, and one shared runner deploys everywhere.
After: patched quickly and holding few secrets worth taking
Only staff reach GitLab, and new accounts wait for approval. Jobs get short-lived credentials from Vault through ID tokens, so the server stores few secrets worth stealing.
The biggest single change is getting long-lived secrets out of CI/CD variables. GitLab jobs can request an ID token with the id_tokens keyword and trade it for a short-lived secret, and GitLab documents a secrets:vault keyword that does this with HashiCorp Vault. A stolen server then gives an attacker much less to take, and what it does give expires. Our HashiCorp Vault page and Terraform page cover how that fits a Terraform pipeline.
Durable fixes, tied to this case
- 01Keep GitLab off the open internet
Put it behind a VPN or an identity-aware proxy. The Alpine attack started with a stranger registering an account.
- 02Require approval for new accounts
GitLab has an admin setting for this under new user account restrictions. GitLab notes it doesn't cover LDAP or OmniAuth sign-ins, which need their own setting.
- 03Patch criticals within a day
Subscribe to GitLab's patch release feed and give critical releases a standing change window. Alpine set itself the same one-day target afterwards.
- 04Own your Ruby if you own your build
Read each GitLab release for Ruby or library patches. If you build GitLab yourself, nobody else will apply them for you.
- 05Log outbound traffic
Alpine couldn't confirm or rule out code execution partly because nothing recorded outbound connections. Ship those logs off the box and keep them.
If you've been breached
If you find signs someone used this
Work in order and save evidence before you change anything. A successful exploit can leave no crash and nothing in the web logs, so a missing signal doesn't mean you're clean. If the server was exposed and unpatched for a while, Alpine's approach is a fair baseline: treat it as compromised and rebuild.
- 01Containfirst hour
- Pull the server off the internet so only your own team can reach it.
- Snapshot the VM or volumes before restarting anything, since memory evidence goes on restart.
- Copy the GraphQL, API, Puma and Sidekiq logs off the box. Some rotate quickly.
- Pause runners that deploy to production.
- 02Investigatefirst day
- Run GitLab's six detections back to October 12.
- Pull the list of accounts created since October 12. Alpine's attacker was a brand-new account, so check what each one touched.
- Check for unexpected files, SSH keys, hooks, scheduled jobs and processes on the host.
- Look at outbound connections from the GitLab host, if anything recorded them.
- 03Rebuildfirst days
- Stand up a clean server on the newest patched build, with a fixed Ruby if you build your own.
- Restore data from a backup you trust, and check repositories against copies held outside GitLab.
- Replace GitLab's own secret keys, as Alpine did, rather than carrying them over.
- 04Rotatebefore reopening
- Reset every user's password and revoke personal access tokens. Alpine reset every password and replaced GitLab's secret keys before it let anyone back in.
- Rotate CI/CD variables, runner tokens, deploy keys and webhook and integration credentials.
- Rotate the cloud keys and database passwords those variables unlocked, and review cloud audit logs for their use.
- 05Report and learnfirst week
- Tell users which passwords and tokens to reset. Alpine also saw logins tried with passwords leaked elsewhere, so anyone who reused theirs should change it there too.
- Bring in legal on notices to customers, regulators and your cyber insurer, depending on what data the server held.
- Write down what slowed you down, whether that was the version mix-up, missing logs or secrets stored in variables.
Code repositories add one more check. If an attacker owned the server, they could have changed code or tags. Alpine checked commits from October 20 to 22 against records outside GitLab, a developer's clone and its build servers, and is extending that check to October 27, when it closed GitLab. If your developers' local clones and your build logs reach back far enough, you can do the same check.
What this could look like: three typical setups
What this could look like: three typical setups
- the setup
- A small software company runs GitLab 19.3.4 from the Linux package on a cloud VM. It's open to the internet, anyone can sign up, and the Terraform pipeline pulls an AWS access key from a CI/CD variable.
- what happens
- Anyone can register and send a crafted CI file. If the exploit works, the attacker reads the AWS key and every private repository, and the Terraform state tells them exactly what the company runs.
- what to do
- Upgrade to 19.3.7 today and turn off open sign-up. Because the server was exposed, run the detections, rotate the AWS key and runner tokens, and move the pipeline to short-lived credentials.
- the setup
- A mid-size company builds its own GitLab container on a standard Ruby base image so it can add internal certificates. Ops upgraded to 19.4.2 on October 14, and the scanner now reports the CVE as fixed.
- what happens
- The new GitLab code is in place, but the Ruby underneath is unchanged, so the regex bug is still there. This is the same gap Alpine found on its own server.
- what to do
- Rebuild the image on Ruby 3.4.12 or 3.3.13 and GitLab 19.4.5, check ruby -v inside the running container, and add Ruby to the image's patch checklist.
- the setup
- An enterprise runs GitLab 18.11.12 behind its VPN. Someone on the security team saw that CVE-2026-97229 starts at 19.2 and closed the ticket.
- what happens
- That part is true, but CVE-2026-110264, also rated 10.0 with no login, reaches 18.11 too. The VPN keeps strangers out, but any contractor laptop or stolen VPN account still gets through.
- what to do
- Upgrade to 18.11.14 in the next change window and plan the move to a 19.x line. Review which contractor accounts can reach GitLab.
All three are made up, and none is a real incident or a Cohash client. The versions and behaviour in them come from GitLab's, Ruby's and Alpine's own pages.
How Cohash helps
The upgrade is usually the easy part. The slow work comes after it, when someone has to find which pipelines hold real keys and move them out of GitLab. Our DevSecOps practice works on CI hardening and image rebuilds, and our Terraform and Vault engineers set up the ID token and Vault pattern described above. If you'd like a second look at your exposure, start with an assessment.
What to take from this
- 01
Someone with no login can run code on a self-managed GitLab server through CVE-2026-97229. CISA lists it as exploited, so don't wait for the next change window.
- 02
Upgrade straight to the October 29 build on your line, so all four regex bugs are closed in one window.
- 03
If you build GitLab yourself, upgrade Ruby to 3.3.13, 3.4.12 or 4.0.9 too. The GitLab upgrade alone won't fix it.
- 04
An 18.11 server isn't safe just because this CVE starts at 19.2. CVE-2026-110264 reaches it.
- 05
If the server was exposed and unpatched, rotate CI secrets and runner tokens even when the detections come back clean.
01Am I affected by CVE-2026-97229?
You are if you run GitLab yourself on 19.2 before 19.2.8, 19.3 before 19.3.5 or 19.4 before 19.4.2. GitLab.com and GitLab Dedicated were patched before the advisory. If you built GitLab on your own Ruby, check the Ruby version as well, because the fix sits in the Ruby that GitLab ships.
02Which GitLab version should I upgrade to?
Go to the newest build on your line from October 29: 19.5.3, 19.4.5, 19.3.7, 19.2.10, 19.1.10, 19.0.11 or 18.11.14. Those include the fix for this CVE, the related CVE-2026-110264 and a certificate fix. Older lines than 18.11 got no backport, so they need a bigger upgrade.
03Does upgrading GitLab fix it if I run my own container image?
Not by itself. GitLab applies the fix as a patch to the Ruby it bundles in its packages and official images. If your image uses standard Ruby, also move to Ruby 3.3.13, 3.4.12 or 4.0.9. Alpine Linux upgraded to 19.3.6 and was still vulnerable for that reason.
04Do I need a login to exploit it?
GitLab rates CVE-2026-97229 as needing no login. Two earlier bugs in the same family did need one. In the attack Alpine described, the attacker registered a fresh account first, which is why open sign-up matters.
05How do I tell if someone tried it on my server?
GitLab published six detections that read its Puma, Rails and Workhorse logs. In the Puma log you're looking for native worker crashes that mention Onigmo. The GraphQL log can show a hostile pattern sent to CI lint as a variable, and the API log shows scripted bursts of failed lint calls. GitLab and Alpine both warn that a successful exploit can leave no crash behind.
06Does this affect other Ruby apps?
It can. Ruby says any program that compiles a regular expression from a string an attacker controls is exposed, and CISA's note says other products could be affected. Apps that only use patterns their own developers wrote aren't affected. The fix is Ruby 3.3.13, 3.4.12 or 4.0.9.
07What should I rotate if my server was exposed?
Everything the server held: user passwords, personal access tokens, CI/CD variables, runner tokens, deploy keys, and webhook and integration credentials. Then rotate the cloud keys and database passwords those variables unlocked, and replace GitLab's own secret keys if you rebuild.
Three questions, about a minute
Pick an answer and we'll tell you if it's right, and why it is or isn't. The score stays in this browser.
-
Q1Your team builds its own GitLab image on a standard Ruby base. Someone upgraded it to 19.4.2 last week, and the scanner now says it's fixed. What's left?
show the answer
B. Rebuild the image on Ruby 3.3.13, 3.4.12 or 4.0.9, because GitLab's fix lives in the Ruby it ships GitLab fixed this by patching the Ruby inside its own packages and images. A self-built image keeps whatever Ruby it was built with, and Ruby says Regexp.timeout doesn't protect against these bugs.
-
Q2A server runs GitLab 18.11.12 behind a VPN. CVE-2026-97229 only affects 19.2 and later. Is it done?
show the answer
C. No. Its sibling CVE-2026-110264 still hits 18.11, so upgrade to 18.11.13 or later The headline CVE starts at 19.2, but the sibling GitLab fixed on October 21 goes all the way back to 7.0. A VPN cuts down who can try it. Anyone already on the VPN still can.
-
Q3Your unpatched GitLab sat open to the internet, sign-up on, from October 12 until today. GitLab's detections find nothing. What about the CI secrets?
show the answer
A. Rotate them, because a successful exploit may leave no trace GitLab says its GraphQL rule catches probes rather than a successful run, and Alpine notes a working exploit leaves no crash. If the exposure was real, rotation is the safe call.