Inside the GitLab Flaw: From CI Configuration to Server Execution (CVE-2026-97229)
Vulnerability Analysis · CVE-2026-97229
A CVSS 10.0 bug in GitLab’s regular-expression compiler is now being exploited. This guide covers who is exposed, which version to upgrade to now (it is not the one in the original advisory), how the attack works, and how to look for signs of earlier compromise.
GitLab sits at the center of the software supply chain. It holds source code, runs builds and often keeps credentials that reach all the way into production. When a flaw in that platform lets an outsider run commands on the server, patching it is only half the job. The other half is working out whether someone got in first.
On October 29, 2026, the U.S. Cybersecurity and Infrastructure Security Agency (CISA) added CVE-2026-97229 to its Known Exploited Vulnerabilities (KEV) catalog. That confirms the flaw is being exploited in the wild.4 GitLab released the fix on October 12.1 Rapid7 published a full technical analysis and a public proof of concept days later.7,8 Any self-managed instance that is still unpatched should be treated as a priority incident.
Points clés à retenir
- Upgrade to GitLab’s October 29 releases (19.5.3, 19.4.5, 19.3.7, 19.2.10 or the matching release for your branch). The first fixes for this CVE (19.4.2, 19.3.5, 19.2.8) are still exposed to a second CVSS 10.0 regex-compiler flaw, CVE-2026-110264, patched on October 21.2,3
- The flaw gives code execution on the GitLab application server as the
gitservice account, not just inside a CI job. Rapid7 reproduced this on gitlab-ce 19.4.1 with ASLR enabled.7 - Instances that allow open sign-up and public project creation are the easiest to attack. In the route Rapid7 demonstrated, the attacker needs to push to a public project first, then sends an unauthenticated CI lint request.6,7
- Patching is not the same as clearing an incident. If your instance was exposed before you patched, investigate for prior compromise and review every credential the server could reach.
Active exploitation is confirmed by CISA’s KEV listing. As of October 31, the sources we reviewed do not name an attacker or a campaign. They also contain no victim timeline and no verified list of attack IP addresses or file hashes. The attack sequence below is Rapid7’s lab reproduction, and real attacks may take another route. The commands in this guide are defensive examples, not output from a real incident.
01 / EXPOSUREWho needs to act, and which version to install
GitLab rates CVE-2026-97229 Critical, CVSS 3.1 score 10.0 (vector AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H). It affects both Community and Enterprise editions. The vulnerable ranges are 19.2 before 19.2.8, 19.3 before 19.3.5, and 19.4 before 19.4.2. GitLab.com already runs the patched code. GitLab patched all GitLab Dedicated tenants before the advisory was published.1 The CVE record classifies the weakness as CWE-190, integer overflow or wraparound. The researcher joaxcar reported it through GitLab’s HackerOne bug bounty program.5
On October 21, GitLab patched CVE-2026-110264, another CVSS 10.0 integer overflow in the regex compiler, reported by the same researcher. It affects every version from 7.0 up to the October 21 releases. That means an instance upgraded only to 19.4.2, 19.3.5 or 19.2.8 is still critically exposed.2 GitLab then shipped further patch releases on October 29.3 Those are the versions to install today.
| Branch | First fix for CVE-2026-97229 (Oct 12) | Also fixes CVE-2026-110264 (Oct 21) | Recommended target (Oct 29) |
|---|---|---|---|
| 19.5 | Not in affected range | 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 in affected range | 19.1.9 | 19.1.10 |
| 19.0 | Not in affected range | 19.0.10 | 19.0.11 |
| 18.11 | Not in affected range | 18.11.13 | 18.11.14 |
Sources: GitLab patch releases of October 12, 21 and 29, 2026.1–3 The October 29 release also says self-managed GitLab Runner and AI Gateway must be updated separately. Check GitLab’s releases page for anything newer before you schedule maintenance.
The exposed population is large. Censys data reported on October 14 counted roughly 25,800 vulnerable GitLab hosts, about 31% of the GitLab hosts it observed. The largest numbers were in Germany, Russia and the United States.16
02 / ATTACK PATHHow repository content becomes server execution
The bug sits in Onigmo, the regular-expression engine built into Ruby, and in how GitLab uses it. GitLab compiles regular expressions from CI configuration, such as rules:exists et rules:changes patterns. That means text an attacker controls ends up being compiled on the server.7
From repository input to application execution
- Attacker prepares a public projectCommits a malicious
.gitlab-ci.ymland a file path with unusual four-byte UTF-8 characters. - Unauthenticated CI lint requestA request to the CI lint endpoint makes GitLab check the project’s paths against the attacker’s regex.
- The guard is bypassedAn inline comment hides a large repeat count from GitLab’s digits-only size check, so the pattern passes.
- Recompilation and overflowThe UTF-8 path forces the pattern to be compiled again, outside the timeout and size checks. The program-size calculation overflows a signed 32-bit integer.
- Command execution in the Puma workerThe broken compiled code redirects execution to
system(), which runs commands as thegituser. The worker then crashes and is replaced.
Two details matter for defenders. First, the attack needs write access to a project. GitLab’s PR:N rating describes the final request, which needs no login. But Rapid7 notes that its route first requires pushing a commit, and that this does not fit neatly with a “no privileges” rating. On an instance with open sign-up and public project creation, getting that access is trivial.6,7
Second, this is code execution on the application server, not in a disposable CI runner. Rapid7 achieved command execution in the Rails/Puma worker as the git account, with ASLR enabled. Its lab exploit needed around 2 GiB of extra memory and offsets tuned to the specific build. That is a real barrier, but a well-resourced attacker can get past it. The exploit does not by itself give root access.7
03 / ROOT CAUSEWhy a regex timeout is not a fix
On October 23, the Ruby core team published an advisory covering four regex-engine bugs found through GitLab’s bug bounty program: CVE-2026-89078, CVE-2026-93577, CVE-2026-97229 and CVE-2026-110264. The advisory says CVE-2026-97229 comes from an integer overflow in the compiled pattern size. It recommends Ruby 4.0.9, 3.4.12 or 3.3.13 or later, the releases that contain all four fixes. On the 4.0 branch, the fix for this specific overflow first shipped in 4.0.8.9
Ruby’s guidance is clear. Only applications that compile regular expressions from attacker-controlled strings are exposed, for example through Regexp.new, Regexp.union ou /#{input}/. Patterns written by developers as constants are not affected. Regexp.timeout does not protect against these bugs. Limiting pattern length is also unreliable, because some of them can be triggered by short patterns.9
The upstream fix, Ruby pull request #19020, makes two changes. It replaces a multiplication that could overflow (the pattern length times the repeat count) with a comparison based on division, so the dangerous product is never calculated. It also caps the compiled program’s size while it is being built, raising a RegexpError before jump offsets can wrap around.10 The general lesson: once a value has overflowed, checking it afterwards cannot prove it was safe. The check has to happen before the overflow.
Upgrade GitLab through its supported distribution (the Linux package, the official container image or the Helm chart). Updating the host operating system’s ruby package does not update the Ruby that GitLab bundles, your container images or every application node. Other Ruby applications that compile user-supplied regular expressions need their own assessment against Ruby’s advisory.
04 / INVESTIGATIONBuild a timeline before drawing conclusions
Start your investigation window on the date a vulnerable version first became reachable by untrusted users, not on the date CISA announced exploitation. Rapid7’s proof of concept has been public since mid-October, so the window may be wider than you expect.8 Keep that window open until containment and patch verification are complete. Record every timestamp in UTC, along with the source time zone, the host or pod it came from, and any clock drift. Store repository commits and project IDs alongside request correlation IDs so events can be matched up later.
Correlate three independent evidence layers
- Long-running
…/ci/lintcalls - HTTP 502 / “badgateway” EOF in Workhorse
- Proxy and WAF logs
- Puma worker restarts
regexp_matcher.rbin traces- New CI configuration commits with odd paths
- Shell started by the Puma process
- Sudden memory jump of about 2 GiB
- New outbound connections
| Signal | How to interpret it |
|---|---|
Long GET /api/v4/projects/<id>/ci/lint?content_ref=… near a worker crash | Matches the demonstrated route, but ordinary lint traffic is common. |
| HTTP 502 / “badgateway: failed to receive response: EOF” plus a Puma restart | A useful correlation. Not proof of compromise on its own. |
regexp_matcher.rb in a saved trace | Supports a regex-path investigation. Not proof on its own. |
| Commit with a comment-split repeat, POSIX character classes and four-byte UTF-8 path names | A strong lead when it doesn’t match the project’s normal activity. |
| Unexpected shell or interpreter started by a Puma worker | Escalate if it can’t be explained. Rule out legitimate maintenance first. |
| New tokens, users, SSH keys, webhooks or pipeline changes | General post-compromise hunting. Not specific to this CVE. |
Rapid7 says other exploitation paths may look different. Not every successful exploit has to crash a worker.7
GitLab also publishes six detections for this CVE in its open-source gitlab-security-oss/tldr repository: onigmo_native_worker_crash, graphql_cilint_hostile_regexp, ci_regexp_oversized_non_ascii_path, ci_regexp_guard_rejection, ci_lint_workhorse_worker_crash et ci_lint_scripted_client_error_burst.11 Take the rule logic directly from that repository and test it against your own log schema before relying on it.
05 / HANDS-ONDefensive command-line triage
These examples are for Linux package (Omnibus) installations unless stated otherwise. They are built around documented GitLab commands and log locations. They are not a vendor-certified scanner, and they have not been run against a live incident. Work on preserved copies where you can, because inspecting a live system changes its state. Keep all output private: logs, process lists and environment data can contain secrets.
sudo gitlab-rake gitlab:env:info sudo gitlab-ctl status
Both are documented GitLab commands.12,13 Run them on every Rails application node. Record the GitLab version, revision and bundled Ruby version, and compare each node and container image digest with the version you intended to deploy. A healthy service status shows the service is up. It does not show the server was never compromised.
sudo find /var/log/gitlab -maxdepth 2 -type f \ -printf '%TY-%Tm-%TdT%TH:%TM:%TS %s %p\n' | sort
Log paths and rotation behavior follow GitLab’s log documentation.14 Uses GNU find syntax.
# Point this at a restricted COPY of the collected logs
LOG_COPY='/mnt/evidence/gitlab-logs'
rg -n -i --text \
'ci/lint|cilint|content_ref|regexp_matcher\.rb|onigmo|badgateway|failed to receive response|segmentation fault' \
"$LOG_COPY"
Requires ripgrep. Expect a lot of false positives. No match does not mean the system is clean. Compressed archives need the next step.
# .gz archives (logrotate) and svlogd '@….s' archives
find "$LOG_COPY" -type f \( -name '*.gz' -o -name '@*.s' \) -print0 |
while IFS= read -r -d '' f; do
gzip -cdf -- "$f" | rg -n -i --label="$f" -H \
'ci/lint|regexp_matcher\.rb|onigmo|badgateway|EOF'
done
GitLab documents both .gz and svlogd .s archives.14 Check each archive’s format first, and leave the originals untouched.
Prioritize the gitlab-workhorse, puma et gitlab-rails logs, together with reverse-proxy, load-balancer and central SIEM logs. For JSON logs, pull out the time, path or URI, status, remote address and correlation_id, using the field names your version actually writes. Field names vary between components and versions. Interpret client IP addresses in light of your trusted-proxy configuration.14
date -u '+%Y-%m-%dT%H:%M:%SZ'
sudo ps -eo user,pid,ppid,lstart,args --forest
sudo ss -tpn
# Set the start date to the beginning of your exposure window
sudo journalctl -k --since '2026-10-01 00:00:00 UTC' --no-pager
Look for a shell, interpreter or download tool (sh, bash, curl, wget, python) running under a Puma worker as git, especially one with outbound connections. Current process and socket lists will not show activity that has already ended. Kernel logs may explain a crash or out-of-memory event, but they cannot show who caused it.
# Never run the project's pipeline or scripts during analysis
git --no-pager log --all --date=iso-strict \
--format='%H %ad %an %s' --name-status -- .gitlab-ci.yml
git --no-pager show --no-ext-diff --no-textconv '<SUSPECT_COMMIT>:.gitlab-ci.yml'
git -c core.quotePath=true ls-tree -r --name-only '<SUSPECT_COMMIT>'
With core.quotePath=true, non-ASCII path names are shown as escaped bytes, which makes four-byte UTF-8 characters easy to spot. Also check any CI files the configuration includes and any deleted branches. A clone may not contain deleted or unreachable objects, so pull those from server-side evidence. Git author names and timestamps can be set by the attacker. Confirm them against GitLab’s server-side push and authentication records.
06 / RESPONSEContainment, recovery and prevention
A response that closes both the flaw and the incident
- Contain and preserveRestrict access at the network edge. Collect volatile data and export logs before retention expires.
- Patch the real deploymentFollow the required upgrade stops, then confirm the version on every application node and image.
- Investigate trust and secretsReview repositories, identity events, runners and deployment access the server could reach.
- Recover and validateRebuild if compromised, revoke exposed credentials, and monitor for recurrence.
Contenir
If you suspect active compromise, restrict access to the instance at a controlled network boundary and bring in incident responders. Collect volatile evidence if you can do it without delaying urgent containment. Export central logs before retention expires. Don’t publish raw CI configurations, process output or secrets as “proof”.
Correctif
Upgrade to the October 29 release for your branch, or anything newer (see Section 01). Follow GitLab’s required upgrade stops (for the 19.x series these are 19.2, 19.5, 19.8 and 19.11) and let background migrations finish between stops.15 The October 12 release includes database migrations, so single-node instances should plan for downtime.1 A backup taken after a known compromise is evidence. Don’t assume it is safe to restore from.
Reduce exposure while you patch
Rapid7 recommends restricting anonymous access to public projects and stopping untrusted users from creating or writing to projects. Rapid7 is explicit that this does not fix the compiler bug and may not block other routes.6 In practice, that means turning off open sign-up (or requiring admin approval), limiting who can create projects, and setting default and restricted visibility levels. MFA protects accounts, but it does nothing to stop an unauthenticated request to the vulnerable endpoint.
Credentials
List every token, CI/CD variable, SSH key, cloud identity and deployment credential the compromised service could access. After containment, revoke anything exposed and issue replacements through a trusted control plane. Check downstream audit trails, such as your cloud provider, container registry and package registries, for any use of the old credentials. Don’t limit the search to the GitLab host.
Recovery
If compromise is confirmed, rebuild the affected application infrastructure from trusted images. Restore data only after reviewing it, check the integrity of repositories and pipelines, and re-establish trust in runners and deployment integrations. Keep the originals for the investigation. Changing passwords and restarting services will not remove every possible form of persistence.
| Preventive control | Purpose |
|---|---|
| Restrict sign-up and public project creation | Cuts down write access for untrusted users. Keep only the access the business actually needs. |
| Segment the application, runners and production | Limits lateral movement. Avoid privileged runner configurations unless they’re required. |
| Short-lived, narrowly scoped deployment identities | Makes stolen build credentials less valuable and shortens their lifetime. |
| Protect CI configuration and release approvals | Makes unauthorized pipeline changes harder to hide. |
| Forward logs and endpoint telemetry off-host | Keeps your visibility even if the server itself is tampered with. |
| Track bundled runtimes and image digests | Confirms the component you actually run got the fix, instead of assuming an OS update covered it. |
07 / CLOSUREWhen can you call the incident resolved?
Reach three separate conclusions, and record each one on its own:
- Vulnerability remediatedEvery application node and image runs a release that fixes both CVE-2026-97229 and CVE-2026-110264, and this has been checked rather than assumed.
- Investigation completed within the available evidenceRecord which nodes, date ranges, log sources and credentials were reviewed, and which telemetry was missing.
- Recovery validatedCredentials are rotated, integrations are trusted again, and monitoring is in place to spot a recurrence.
A clean search means no matching evidence was found in the sources you checked. It does not prove the system was never accessed. Say that plainly in your report.
Treat GitLab as production security infrastructure, not a developer convenience. An effective response to CVE-2026-97229 has three parts. First, an application update you have verified, to a release that also fixes CVE-2026-110264. Second, evidence correlated across the request, application and host layers. Third, a disciplined review of the delivery credentials that make this platform so valuable to attackers.
REFERENCESSources primaires
- GitLab Critical Patch Release: 19.4.2, 19.3.5, 19.2.8 — GitLab, Oct 12, 2026
- GitLab Critical Patch Release: 19.5.1 (CVE-2026-110264) — GitLab, Oct 21, 2026
- GitLab Patch Release: 19.5.3 — GitLab, Oct 29, 2026
- CISA adds two Known Exploited Vulnerabilities to catalog — CISA, Oct 29, 2026
- CVE-2026-97229 record — CVE Program (CNA: GitLab)
- CVE-2026-97229: Critical unauthenticated remote code execution in GitLab — Rapid7, Oct 13, 2026 (updated Oct 30)
- GitLab CI/CD regex integer overflow: technical analysis — Rapid7, Oct 16, 2026
- CVE-2026-97229 proof of concept — Jonah Burgess / Rapid7, GitHub
- Regexp fixes for GitLab CVEs — Ruby core team, Oct 23, 2026
- Avoid overflow in the regexp compiler’s program size arithmetic (PR #19020) — ruby/ruby, GitHub
- GitLab Security detections (tldr) — GitLab Security, GitLab.com
- Maintenance Rake tasks — GitLab Docs
- Linux package maintenance commands — GitLab Docs
- Log system — GitLab Docs
- Plan your upgrade path — GitLab Docs
- Coverage of Censys exposure data for CVE-2026-97229 — Daily Security Review, Oct 14, 2026
Methodology. Sources were checked on October 31, 2026. CISA’s KEV catalog page could not be retrieved directly, so this article confirms the KEV addition from CISA’s bulletin and the CVE record. It does not state a federal remediation deadline. GitLab’s detection rules are identified by name only, because their full contents were not independently validated. Figures are original MAG212 CYBER explanatory graphics based on the cited material. They are not vendor screenshots or incident evidence. No exploit was run and no production environment was examined for this article.
Get the next analysis first
Practical security research and defensive guidance, free in your inbox.
