CVE-2026-97229, the CVSS 10 flaw in GitLab’s CI/CD regular-expression compiler that GitLab patched on 12 October, entered CISA’s Known Exploited Vulnerabilities catalog on 29 October with a federal due date of 1 November, two weeks after Rapid7 published a working proof of concept. Shadowserver still counted more than 1,000 vulnerable servers online over the weekend, and the fix has a catch that bit Alpine Linux: it is a patch to the Ruby that GitLab bundles, so a GitLab that runs on its own Ruby stays exploitable after the upgrade.
In my 21 October security-day roundup this bug got one line, and its successor CVE-2026-110264 one card. Since then it has picked up a public exploit, a documented attack on Alpine Linux’s GitLab and a three-day federal deadline. Below: what changed, what it means, and this week’s work: read-only checks, upgrades per install type and what to do if you can’t patch today. Versions and commands come from GitLab’s advisories and docs, Ruby’s announcement, CISA’s KEV feed and my own reading of the omnibus-gitlab release tags on 3 November.
1. Context: From Patch Note to KEV Entry in 17 Days
GitLab’s 12 October advisory describes the flaw in one sentence: an unauthenticated user could run arbitrary code on the server ‘via a specially crafted regular expression in a CI/CD configuration, due to insufficient regular expression validation.’ It affects CE and EE 19.2 before 19.2.8, 19.3 before 19.3.5 and 19.4 before 19.4.2, scores CVSS 3.1 10.0 and was reported by joaxcar through HackerOne. It is also a bypass: GitLab’s detection notes say the 23 September fix for CVE-2026-93577 was incomplete. Underneath, both are the same integer overflow in Onigmo, the regex engine built into Ruby, which Ruby fixed upstream on 23 October in 3.3.13, 3.4.12 and 4.0.9.
Since then the threat picture has changed. On 13 October Rapid7 published a proof of concept that runs commands as the git user on the official 19.4.1 CE image. On 20 and 21 October a newly registered account sent crafted CI configurations to Alpine Linux’s GitLab, crashing its workers eight times; Alpine found no proof the code ran, but rebuilt the server. On 29 October CISA added the CVE to its KEV catalog with a 1 November due date for US federal agencies, the three-day window BOD 26-04 gives a bug that is automatable, grants total control and sits on an internet-facing asset.
Timeline (dates as published by each source)
How each source rates it
| Source | Rating | What to read into it |
|---|---|---|
| GitLab (CNA, CVE record) | CVSS 3.1 10.0, Critical | PR:N, UI:N: no privileges or user interaction |
| Rapid7 | RCE reproduced, PoC public | Its route needs write access to a public project, which ‘conflicts with GitLab’s PR:N rating’ |
| CISA KEV | Added 29 Oct, due 1 Nov | CWE-190, forensic triage required, ransomware use ‘Unknown’ |
| CISA SSVC | Active, automatable, total impact | The mix that earns a three-day KEV deadline on internet-facing assets |
| Ruby core | No CVE requested | Track the fix under GitLab’s four CVE IDs |
| Debian | ruby3.3, ruby3.1 vulnerable | No fixed package when I checked on 3 November |
The fix is a Ruby patch, not GitLab code
GitLab’s 21 October advisory says it in so many words about CVE-2026-110264: ‘The fix … is a patch to Ruby, not to GitLab code, and it is only included in official GitLab Linux packages and container images.’ CVE-2026-97229 works the same way: the 12 October notes list ‘Add Ruby regexp program size patch for 3.3 and 3.4’, and GitLab’s self-compiled guide now says upgrading GitLab alone does not apply the fix.
Where things stand (checked 3 November 2026)
Fixed versions
GitLab’s advisories, the CVE records and the omnibus-gitlab release tags agree on 19.4.2, 19.3.5 and 19.2.8, and on the 21 October releases for CVE-2026-110264. The newest patch on every line is from 29 October.
KEV listing
From CISA’s KEV JSON feed: added 29 October, due 1 November, forensic triage ‘Yes’, and a note that ‘other products can potentially be affected by the same underlying Ruby/Onigmo Integer Overflow issue’.
Exploitation in the wild
CISA doesn’t publish its evidence. The public account is Alpine’s: eight worker crashes, four that ‘got partway through the exploit and failed at the next step’, and no proof of success. I found no confirmed compromise, which is absence of evidence, not evidence of absence.
Exposure
BleepingComputer, citing Shadowserver on 1 November: more than 1,000 instances vulnerable to CVE-2026-97229 and more than 11,300 to CVE-2026-110264, out of about 39,000 GitLab instances seen daily.
Still open
Rapid7 doesn’t know how the private report avoids the write-access step; GitLab’s issues go public 90 days after each fix. CVE-2026-110264 isn’t in KEV, and Debian’s Ruby has no fix yet.
Golden rule
Judge a GitLab server by who built its Ruby. On official packages, images and charts, the GitLab version is the fix; on anything with its own Ruby, the fix is the GitLab version plus Ruby 3.3.13, 3.4.12 or 4.0.9 or later in the same series.
2. Five Implications for Teams That Run GitLab
Rapid7’s technical analysis and GitLab’s published detection rules are the two best maps of how the bug is reached and what an attempt leaves behind. Read alongside Alpine’s write-up, they change how I would prioritise it.
The KEV deadline has already passed
CISA KEV · BOD 26-04
The federal due date was Sunday 1 November, three days after the listing. For everyone else that reads as ‘today’: an internet-facing GitLab on 19.2 to 19.4 below the fixed releases faces a public exploit, and may have been probed since mid-October.
‘Unauthenticated’ in practice means ‘anyone who can sign up’
Rapid7 · Alpine · GitLab detections
GitLab rates it PR:N, but Rapid7’s working route needs a CI file committed to a public project the attacker can write to; only the final CI lint request skips credentials. Alpine’s attacker used a newly registered account. With open sign-up and public projects, assume that prerequisite is met.
The fix lives in Ruby, so the GitLab version can mislead
GitLab advisory · Ruby · Alpine
Official packages, images and charts ship a Ruby GitLab patched itself; self-compiled installs and community images bring their own, which a GitLab upgrade doesn’t touch. Alpine made both mistakes: it installed 19.3.4 instead of 19.3.5, then moved to 19.3.6 on its own Ruby and found the server still vulnerable on 26 October.
12 October is not enough, and 110264 is the wider exposure
GitLab · Shadowserver
CVE-2026-110264, fixed on 21 October, is a second CVSS 10 Onigmo bug, reached by rendering Org-mode files, and it affects every version from 7.0. It isn’t in KEV, but Shadowserver counts about eleven times more servers vulnerable to it. A server patched on 12 October is still exposed.
Exploits are build-specific, and attackers fetch your build
Rapid7 · Alpine
Rapid7 validated its chain on one image, official 19.4.1 CE on amd64, and says other builds need adjusted offsets. No comfort for custom builds: Alpine says its attacker ‘downloaded our GitLab container image, which suggests they were tuning the exploit to our exact build.’ Failed attempts still crash Puma or Sidekiq workers, your best early signal.
Which release fixes what, per GitLab line
| Line | CVE-2026-97229 (12 Oct) | CVE-2026-110264 (21 Oct) | Latest patch (29 Oct) | Bundled Ruby, as ruby -v reports it |
|---|---|---|---|---|
| 19.5 | Not affected | 19.5.1 | 19.5.3 | 3.3.12, patched by GitLab |
| 19.4 | 19.4.2 | 19.4.3 | 19.4.5 | 3.3.12, patched by GitLab |
| 19.3 | 19.3.5 (not 19.3.4) | 19.3.6 | 19.3.7 | 3.3.12, patched by GitLab |
| 19.2 | 19.2.8 | 19.2.9 | 19.2.10 | 3.3.11, patched by GitLab |
| 19.1 · 19.0 | Not affected | 19.1.9 · 19.0.10 | 19.1.10 · 19.0.11 | 3.3.11, patched by GitLab |
| 18.11 | Not affected | 18.11.13 | 18.11.14 | 3.3.10, patched by GitLab |
| 18.10 and older | Not affected | No backport | Upgrade path to 18.11.14 or later | Unpatched |
Where the fix lives, per install type
| Install type | Where its Ruby comes from | What fixes CVE-2026-97229 |
|---|---|---|
| Linux package (deb/rpm) | Bundled in /opt/gitlab/embedded, patched by GitLab | Upgrading GitLab to a fixed release |
| Official Docker image (gitlab/gitlab-ce, gitlab/gitlab-ee) | The Linux package inside the image | Pulling a fixed tag and recreating the container |
| Helm chart (cloud-native images) | CNG images, patched by GitLab | Upgrading to a chart version mapped to a fixed release |
| Self-compiled (source) | The system Ruby you installed | Upgrading GitLab and Ruby to 3.3.13, 3.4.12 or 4.0.9 or later |
| Community or in-house images, alpinelinux/gitlab included | Whatever the Dockerfile installs | Upgrading GitLab and rebuilding on a fixed Ruby |
| GitLab.com · GitLab Dedicated | Managed by GitLab | Nothing to do: already patched |
Some outlets list 19.3.4 as fixed; GitLab’s advisory and the CVE record say 19.3.5. Beyond GitLab, Ruby says any app that compiles regexps from attacker-controlled strings (Regexp.new, Regexp.union, Regexp.compile, /#{input}/) is exposed and that Regexp.timeout doesn’t protect; apps that only use their own regexps are not affected.
3. What to Do This Week
Same order as any server bug in KEV: find out what you run, save the evidence, patch the way your install type demands, and close the doors the exploit uses until you can. Everything in step 1 only reads.
Step 1: Find out what you run, and whose Ruby it uses
GitLab version per install type
The rake task, API endpoint and chart mapping come from GitLab’s docs; the in-app Help > Help page also shows the version to signed-in users. On a Linux package the version answers the question, because the patched Ruby ships inside it. Swap in your own container name.
# Linux package: read 'GitLab information > Version' sudo gitlab-rake gitlab:env:info # Official Docker image: the same task inside the container docker exec gitlab gitlab-rake gitlab:env:info # Any install type, from your laptop (needs an access token) curl --request GET \ --header "PRIVATE-TOKEN: <your_access_token>" \ --url "https://gitlab.example.com/api/v4/version" # Helm chart: chart versions are not GitLab versions, # map them on the chart version mappings page helm list -n <namespace> # Self-compiled: from /home/git/gitlab, as the git user bundle exec rake gitlab:env:info RAILS_ENV=production # fixed for CVE-2026-97229: 19.4.2, 19.3.5, 19.2.8 # fixed for both regex CVEs: 19.5.1, 19.4.3, 19.3.6, 19.2.9, 19.1.9, 19.0.10, 18.11.13
Whose Ruby is it? Containers, custom images and source installs
/opt/gitlab/embedded/bin/ruby is in the Linux package’s file list, and version-manifest.txt is where GitLab’s docs say the bundled versions are recorded. On a GitLab package, expect 3.3.10 to 3.3.12 even when fixed. Anywhere else, the Ruby number is the test.
# Linux package or official image: bundled Ruby, patched by GitLab
/opt/gitlab/embedded/bin/ruby -v # 3.3.12 on 19.3 to 19.5, even when fixed
grep -iw ruby /opt/gitlab/version-manifest.txt
# Each running GitLab container: Linux-package image, or its own Ruby?
for c in $(docker ps --format '{{.Names}} {{.Image}}' | awk 'tolower($2) ~ /gitlab/ {print $1}'); do
if docker exec "$c" test -x /opt/gitlab/embedded/bin/ruby; then
echo "$c: Linux-package image, judge it by the GitLab version"
else
echo "$c: own Ruby: $(docker exec "$c" ruby -v 2>/dev/null || echo 'ruby not on PATH')"
fi
done
# Self-compiled or custom image: this number must reach the fix in its series
ruby -v # fixed: 3.3.13, 3.4.12, 4.0.9 · NOT fixed: 3.4.10, 3.4.11 · 3.2: no fix
apk info -v 2>/dev/null | grep '^ruby-[0-9]' # Alpine: 3.3.13-r0 (3.21), 3.4.12-r0 (3.22+)
dpkg -l 'ruby3*' | awk '/^ii/ {print $2, $3}' # Debian: ruby3.3 and ruby3.1 unfixed on 3 NovDon’t judge a GitLab package by ruby -v
I read the omnibus-gitlab release tags: 19.4.2 and 19.3.5 patch Ruby 3.3.12 with regexp-program-size, 19.4.3 adds regexp-repeat-id-overflow, and the version string never changes. A fully patched 19.4.5 still bundles Ruby 3.3.12, below the 3.3.13 Ruby calls fixed, so a scanner that flags it is reading the number, not the patch. The reverse trap is worse: a custom image on GitLab 19.3.6 shows a fixed GitLab over a vulnerable Ruby, exactly what Alpine ran.
Step 2: Patch per install type
Save the logs first (the yellow box below explains why), then upgrade. The 12, 21 and 29 October releases all include database migrations, so single-node instances go down while they run; multi-node instances can use GitLab’s zero-downtime procedure. From 18.11 to 19.5 you don’t need a new minor: the latest patch on your line fixes both regex CVEs.
Linux package (deb/rpm)
docs.gitlab.com/update/package · 29 Oct releases
Install the latest patch for your line, pinning the exact version as GitLab’s docs show. The package backs up the database first unless /etc/gitlab/skip-auto-backup exists; keep your own backup anyway, including /etc/gitlab/gitlab-secrets.json. The 29 October release also asks for GitLab Runner 19.5.1 (eMudhra CA change).
# Debian/Ubuntu (CE: gitlab-ce=<version>-ce.0) sudo apt update && sudo apt install gitlab-ee=19.4.5-ee.0 # RHEL, AlmaLinux, Oracle Linux 9 (.el8 on 8) sudo dnf install gitlab-ee-19.4.5-ee.0.el9 sudo gitlab-rake gitlab:env:info # Version: 19.4.5-ee
Official Docker image
docs.gitlab.com/update/docker · recreate, don’t restart
Back up the database and the secrets file, pull the fixed tag and recreate the container; GitLab reconfigures and migrates on first start. The 19.5.3, 19.4.5, 19.3.7 and 19.2.10 tags (CE and EE) hit Docker Hub on 29 October. Pin an exact tag, not latest.
# Docker Engine: keep your own hostname, ports and volumes sudo docker stop gitlab sudo docker rm gitlab sudo docker pull gitlab/gitlab-ee:19.4.5-ee.0 # ...then the same docker run as before, with the new tag # Docker Compose: set image: gitlab/gitlab-ee:19.4.5-ee.0, then docker compose pull docker compose up -d
Helm chart (cloud-native images)
docs.gitlab.com/charts · version mappings
The CNG images carry GitLab’s Ruby patches too, so upgrading the chart is the fix. Chart versions differ from GitLab’s: 19.5.3 is 10.5.3 or 10.5.4, 19.4.5 is 10.4.5 or 10.4.6, 19.3.7 is 10.3.8 or 10.3.9, 19.2.10 is 10.2.10. Export your values, skip --reuse-values, and use the documented pause-and-resume sequence for zero downtime.
helm repo update helm search repo -l gitlab/gitlab | head helm get values gitlab > gitlab.yaml helm upgrade gitlab gitlab/gitlab \ --version 10.4.6 \ -f gitlab.yaml \ --set gitlab.migrations.enabled=true
Self-compiled installations
Self-compiled install guide · Ruby, 23 Oct
Two upgrades, not one: GitLab along the documented path, and the system Ruby to 3.3.13, 3.4.12 or 4.0.9 or later in the same series, which GitLab’s guide now requires (built from the official source, not through RVM, rbenv or chruby). Then restart every Puma and Sidekiq process, because a running worker keeps the Ruby it started with.
ruby -v # before: ruby 3.3.12 on a source install means vulnerable # build and install Ruby 3.3.13 / 3.4.12 / 4.0.9 from ruby-lang.org, then ruby -v # 3.3.13 or later in your series bundle exec rake gitlab:env:info RAILS_ENV=production # check 'Ruby Version'
Community and in-house images, Alpine’s included
alpinelinux.org · Docker Hub
Rebuild on a fixed Ruby, not just a newer GitLab. On Alpine bases, apk upgrade brings ruby 3.3.13-r0 (3.21) or 3.4.12-r0 (3.22 and later); official ruby images exist as 3.3.13, 3.4.12 and 4.0.9. For alpinelinux/gitlab, Alpine says ‘any 19.3.6 image you pulled before October 27, and all older tags, are vulnerable’. Check every tag with ruby -v.
# In your Dockerfile, pin a fixed Ruby base, for example # FROM ruby:3.4.12-alpine (or 3.3.13 / 4.0.9) docker build --pull --no-cache -t my-gitlab:19.4.5 . docker run --rm --entrypoint ruby my-gitlab:19.4.5 -v
18.10 and older
Upgrade path tool · required stops
No backport of CVE-2026-110264 exists below 18.11, so you have to move. Required stops are 18.2, 18.5, 18.8 and 18.11, then 19.2, 19.5, 19.8 and 19.11 (17.3, 17.5, 17.8 and 17.11 before that). Land on the latest patch of each stop, plan with GitLab’s Upgrade Path tool, and pin the package version at every step.
Step 3: If you can’t patch today
GitLab’s advisories offer no workaround, and Ruby’s (stop compiling regexps from untrusted input) isn’t something you can do inside GitLab. What you can do is make the known route harder. Rapid7 suggests restricting credential-free access to attacker-writable public projects or blocking the CI lint route; GitLab’s detection notes add: limit who can create accounts and tokens, and reduce public visibility where you can.
Close self-registration
Both options are in GitLab’s sign-up restrictions docs, including the Rails console line. Alpine’s attacker came in through a newly registered account.
# UI: Admin > Settings > General > New user account restrictions # clear 'Allow new user accounts' (or tick 'Require admin approval') # Rails console on a Linux package install sudo gitlab-rails console ::Gitlab::CurrentSettings.update!(signup_enabled: false)
Hunt with GitLab’s own log queries
These lines come from the detection rules GitLab linked in both advisories (gitlab-security-oss/tldr), with Linux package paths; on Helm, the same logs live on the Webservice and Sidekiq pods. GitLab calls a Workhorse 502 whose correlation ID never reached api_json.log ‘the single highest-confidence signal in the set’.
WH=/var/log/gitlab/gitlab-workhorse/current APP=/var/log/gitlab/gitlab-rails/application_json.log PUMA=/var/log/gitlab/puma/puma_stderr.log SIDEKIQ=/var/log/gitlab/sidekiq/current # 1. Workers that died on the lint or GraphQL routes (the detonation) grep -E '"uri":"[^"]*(ci/lint|/api/graphql)' "$WH"* | grep -E '"status":502|badgateway' # 2. Patterns the CI regexp guard caught (the probe, logged verbatim) grep 'Gitlab::Ci::Build::Rules::Rule::Clause::RegexpMatcher' "$APP"* \ | grep -E 'rejected at compile time|match timed out|time budget exceeded' # 3. Native Ruby crashes through Onigmo (CI path and Org-mode path) grep -B1 -A20 '\[BUG\]' "$PUMA"* \ | grep -E 'org-ruby|headline\.rb|regexp_matcher\.rb|match_at|onig_search_gpos|onig_compile|compile_tree|compile_length_tree|rb_str_sub_bang' grep 'A worker terminated, shutting down the cluster' "$SIDEKIQ"* # 4. The trigger commit: paths near 255 bytes with a non-ASCII byte # (run it in a clone of each suspect project) git ls-tree -r --name-only HEAD | awk 'length($0) >= 240' | grep -P '[^\x00-\x7F]'
What these mitigations cover, and what they don’t
- Sign-up off: stops new accounts, not existing ones. Review accounts created since 12 October and any that pushed CI files to public projects.
- Public projects: making them private or internal shrinks Rapid7’s route and the Org-mode path of CVE-2026-110264. GitLab documents ‘Restricted visibility levels’ as limiting who can create public projects, so change existing ones by hand.
- Blocking ci/lint: covers Rapid7’s route only; GitLab’s detections also watch the GraphQL ciLint mutation and pipeline creation, and the undisclosed route may differ.
- Regexp.timeout and length limits: don’t help; Ruby says neither protects, and some of these bugs trigger with short patterns.
- If code already ran: rotate what GitLab’s response notes list: the secrets in gitlab-secrets.json, every CI/CD variable, every runner token and every access token.
Save the evidence before you patch
Upgrading restarts every worker, and the crash traces go with them. CISA’s BOD 26-04 guidance puts evidence collection before patching for KEV entries that need forensic triage, as this one does, and GitLab’s notes say to preserve the full crash report before any restart. Copy /var/log/gitlab off the box, then patch. A quiet success leaves no 502, so also look for files owned by git in /tmp, unexpected child processes of Puma or Sidekiq, outbound connections, and changed crontabs or SSH keys for the git user.
The order I’d work in
- 1. Today: internet-facing GitLab on 19.2 to 19.4 below the fixed releases. Save the logs, then move to the latest patch on your line; if you can’t, close sign-ups and review public projects now.
- 2. Also today: every install with its own Ruby: self-compiled servers and community or in-house images. Check ruby -v, upgrade Ruby, rebuild, restart.
- 3. By Friday 6 November: every other line below the 21 October releases. Anything on 18.7 or later that also missed September’s releases is open to CVE-2026-85706, in KEV since 11 September.
- 4. This week: run GitLab’s log queries back to 12 October; treat a hit as an incident and rotate secrets and tokens.
- 5. Ongoing: your own Ruby services that compile user-supplied regexps, Debian’s Ruby fix, and GitLab’s issues for these CVEs, public 90 days after each fix.
Conclusion
Three weeks ago CVE-2026-97229 was a line in a patch release. Now it is a KEV entry with a public proof of concept, a documented attack on a well-run open-source project and more than a thousand servers still exposed. The twist is where the fix lives: in a Ruby GitLab patches inside its own packages, which is how Alpine ended up on a ‘fixed’ GitLab that was still exploitable.
On GitLab’s Linux package, Docker image or Helm chart, this is a normal patch: save the logs, move to the latest release on your line, and both regex CVEs are covered. If you bring your own Ruby, it’s two upgrades, and ruby -v is the number that tells the truth. Either way, closing open sign-up is the cheapest control you can turn on today.
This week in one screen
Check
- • gitlab:env:info or /api/v4/version
- • Official package, or your own Ruby?
- • ruby -v wherever the Ruby is yours
Patch
- • 19.5.3 / 19.4.5 / 19.3.7 / 19.2.10
- • 19.1.10 / 19.0.11 / 18.11.14
- • Ruby 3.3.13 / 3.4.12 / 4.0.9 if you build it
Watch
- • Workhorse 502s on ci/lint and /api/graphql
- • [BUG] crashes in puma_stderr.log
- • New sign-ups and public projects
Sources
- GitLab — Critical Patch Release 19.4.2, 19.3.5, 19.2.8 (12 Oct)
- GitLab — Critical Patch Release 19.5.1 and backports (21 Oct)
- GitLab — Patch Release 19.5.3 and backports (29 Oct)
- GitLab — Critical Patch Release 19.4.1 (23 Sep, CVE-2026-93577)
- GitLab security — Onigmo detection rules (tldr)
- GitLab docs — Self-compiled installation
- GitLab docs — Upgrade paths
- GitLab docs — Sign-up restrictions
- Ruby — Regexp fixes for GitLab’s CVEs (23 Oct)
- CISA — KEV additions of 29 October
- Rapid7 — ETR: CVE-2026-97229 in GitLab
- Rapid7 — Technical analysis of CVE-2026-97229
- Alpine Linux — GitLab security incident
- BleepingComputer — Over 1,000 GitLab servers vulnerable (2 Nov)
- Debian security tracker — CVE-2026-97229
- omnibus-gitlab — Ruby build definition (19-4-stable)



