TL;DR: Three weeks after GitLab shipped the fix, more than 1,000 servers exposed to the internet are still running the vulnerable code. CISA says attackers are already using it. The exploit code is public and free.
What Happened?
GitLab patched CVE-2026-97229 on October 12 in versions 19.4.2, 19.3.5 and 19.2.8. The bug is an integer overflow in the regular expression compiler that reads CI/CD pipeline files. It scores 10.0, the highest severity possible. The request that sets it off needs no login, and it ends with an attacker running commands on the server.
On October 29, CISA added it to the Known Exploited Vulnerabilities catalog and gave federal agencies until November 1 to fix it. Over that weekend, Shadowserver still counted more than 1,000 exposed servers running unpatched code, out of roughly 39,000 GitLab instances it sees daily.
A working exploit went public on October 13. A Metasploit module landed on October 30.
Patched three weeks ago. Still wide open.
GitLab shipped the fix for CVE-2026-97229 on October 12. Shadowserver scans roughly 39,000 GitLab instances every day. Here is how many had not taken it as of the weekend.
The patch that closes CVE-2026-97229 does not close CVE-2026-110264. A server on 19.4.2, 19.3.5 or 19.2.8 is fixed for the exploited bug and still exposed to the other one.
The Patch That Was Not a Patch
Here is the part catching teams out. The overflow does not really live in GitLab. It lives in Onigmo, the regular expression engine built into Ruby.
GitLab ships a patched Ruby inside its own packages, Docker images and Helm chart. Upgrading works there. Build GitLab from source, or run a container image that uses the distribution’s own Ruby, and the GitLab upgrade fixes nothing at all. Ruby itself has to reach 3.3.13, 3.4.12 or 4.0.9.
Ruby released that fix without filing a CVE of its own. So a version scan reads the GitLab number, sees a patched release and reports clean. One Linux distribution project upgraded its server, stayed exploitable, and only found out when its workers crashed eight times across two days. Four of those crashes got partway through the exploit.
You upgraded GitLab. Did you upgrade Ruby?
The overflow does not live in GitLab. It lives in Onigmo, the regular expression engine built into Ruby. Where your Ruby comes from decides whether the GitLab upgrade actually fixed anything.
3.3.13, 3.4.12 or 4.0.9. One Linux distribution’s image ran standard Ruby and stayed exploitable after its GitLab upgrade.Ruby shipped its fix without a CVE of its own, so a version scan reads the GitLab number, sees a patched release and reports clean. Check the interpreter, not just the app: gitlab-rake gitlab:env:info or ruby -v.
What’s the Impact?
A GitLab server holds source code, pipeline secrets, runner tokens and deploy keys. Code execution there reaches well past the box itself. The one project that published its incident report rebuilt the server, reset every password, replaced its secret keys and checked its released packages against outside records.
Four steps from signup to code execution
The final request needs no login at all. What it needs first is a public project the attacker can push to, which open registration hands over for free.
.gitlab-ci.yml puts a regular expression in rules:exists or rules:changes. An inline comment splits the repeat count, so GitLab’s own guard never sees the real number. A file path carrying unusual characters comes along with it.git, which can read database credentials and the instance secrets file. On a miss, the worker dies, the client sees a 502, and memory spikes by roughly 2 GiB.A failure is loud and a success can be silent. Hunt for both: the crashes tell you someone tried, and only host telemetry tells you whether one of the tries landed.
How to Avoid This
Upgrade Past the First Patch
Move to 19.5.1, 19.4.3, 19.3.6 or 19.2.9 or later from GitLab’s release page. These also close a second regex bug the first patch leaves open.
Check the Interpreter, Not the App
Run gitlab-rake gitlab:env:info or ruby -v and confirm Ruby is on 3.3.13, 3.4.12 or 4.0.9.
Shrink What an Attacker Can Reach
- Turn off open registration, or require admin approval for new accounts
- Block anonymous calls to the CI lint routes at your reverse proxy or WAF
Indicators of Compromise: Hunt for Signs of Exploitation
Alert on web worker crashes and on any shell started by the GitLab user. The hunt pack below lists what to look for.
What to hunt for on a GitLab server
- Oversized file path with a non ASCII characterThe public proof of concept commits one path at the 255 byte limit:
a, then 251xcharacters, then/é. Request paths with a segment of 200 or more characters plus a percent encoded non ASCII byte have no normal use. - Comment split repeat count in a patternA repeat count broken up by an inline regex comment, for example
{45(?#x)700,}, hides the real number from GitLab’s guard. Searchgraphql_json.logforciLintvariables matching\{[0-9]*\(\?#or\{[0-9]{4,}[,}]. - Regex guard warnings
Gitlab::Ci::Build::Rules::Rule::Clause::RegexpMatcherinapplication_json.logwith “rejected at compile time”, “match timed out” or “time budget exceeded”. Normal rules filters are short and never trip these.
- Workhorse 502 with no matching Rails entryThe strongest single indicator. A
502orbadgatewayonci/lintor/api/graphqlwhosecorrelation_idnever appears inapi_json.log. The worker died before it could write the line. - Request bodies near 2 MBIn Workhorse’s
read_bytesfield on those same routes. That is the attack carrier. - Native crash reports with regex frames
[BUG]blocks inpuma_stderr.logcontainingonig_compile,compile_length_tree,regexp_matcher.rb,match_atoronig_search_gpos. Alsosegfaulton ruby or puma in kernel logs. - Sidekiq worker terminationA pushed hostile config kills Sidekiq instead of Puma: “A worker terminated, shutting down the cluster”.
- Memory spike and slow requestWorker memory jumping by roughly 2 GiB, with the request running several seconds before it fails.
- Anonymous CI lint requestsCalls to the lint API with no
meta.userfield. Legitimate traffic comes from signed in users and tokens. Prioritize ones that ran several seconds or returned500or502. - Raw client user agents on lint routes
python-requests,python-urllib,curl,Wget,HTTPieor no user agent at all, returning5xx. Excludeglab,go-gitlaband project bot accounts. - New account to public project in under an hourAn account created on or after October 12 that creates a public project and triggers CI lint shortly after.
- Token and branch created then destroyedThe Metasploit module makes a personal access token, pushes a randomly named branch, fires the request, then revokes the token and deletes the branch within minutes. A web login followed by a call to
/api/v4/versionfrom the same address fits its version check. - Your own container image pulled by an unknown accountSeen in the one publicly documented incident, most likely to tune the exploit against that exact build.
- Child processes of the GitLab user
sh,bash,curl,wget,python,perl,ncorbase64spawned by ruby, puma, bundle or sidekiq running asgit. In testing, commands ran asuid=998(git). - Outbound connections from the GitLab hostAnything beyond your mirrors, registries, mail relay and update servers. Port
8000is the public proof of concept’s default listener; the Metasploit example fetches a second stage over port80. - Foreign key in authorized_keysEvery legitimate line in the git user’s
authorized_keysstarts withcommand="…gitlab-shell…". A line without that prefix is an attacker key. - New files, hooks and cron entriesUnder
/opt/gitlab/embedded/service/gitlab-rails/public, incustom_hooksfolders in Gitaly storage, or in cron for the git user. - Credential and supply chain tamperingNew admins, unexpected access tokens, newly registered runners, added deploy keys, changed webhooks, unusual reads of CI/CD variables, and commits, tags or release artifacts published outside normal pipelines.
Sources: GitLab security advisories and TLDR detection repository (Sigma rules, released Oct 30, 2026), Rapid7 technical analysis and proof of concept, Metasploit Framework module documentation, Ruby regexp fix notice of Oct 23, 2026, CISA KEV entry of Oct 29, 2026, Shadowserver exposure reporting, and the published incident report from one affected Linux distribution project.
Patched Is a Claim. Proven Is Evidence.
Over a thousand teams think they are covered right now, and a version scan is the reason. The gap between a patched release number and a server you can prove is safe is exactly where this flaw has been living.
Secure.com AI Teammates work above the stack you already own:
- Red Teammate validates what is genuinely exploitable on your exposed instances instead of trusting a version string
- AppSec Teammate follows the real dependency, so a fix that lands in Ruby cannot hide behind an application version number
- Cloud Security Teammate surfaces the instances nobody mentioned were facing the internet
- SOC Teammate hunts the worker crashes and anonymous lint calls across your logs, then assembles the case
- Consequential actions wait for your approval, and every step is kept as evidence
Attack. Harden. Prove. Repeat.