CVE-2026-97229 has a CVSS score of 10.0. It is an integer overflow in the regular expression engine that GitLab uses to evaluate CI/CD configuration, and it lets an unauthenticated attacker run commands on the GitLab server as the GitLab application user. GitLab fixed it on October 12, 2026. The listing followed an attack on the Alpine Linux project’s GitLab server on October 20 and 21 that crashed GitLab workers eight times and came close to working. A GitLab server holds source code, CI/CD secrets, runner tokens, deploy keys and the pipelines that build your software. A compromise reaches well past the server itself. Affected and fixed versions Branch Affected Fixed 19.4 before 19.4.2 19.4.2 19.3 before 19.3.5 19.3.5 19.2 before 19.2.8 19.2.8 This applies to self-managed GitLab CE and EE. GitLab.com is already patched, and GitLab Dedicated customers need no action. The same patch release fixes more than 20 other issues, several rated High, so update even if you consider this flaw out of reach. The Ruby pitfall The bug sits in Onigmo, the regular expression engine built into Ruby. GitLab’s fix ships as patches to the Ruby bundled in its official packages and container images. That has two consequences: Official Omnibus packages, official Docker images and the Helm chart (CNG images): upgrading GitLab to a fixed version is enough. Check the exact version; Alpine Linux upgraded to 19.3.4 instead of 19.3.5 and stayed vulnerable. GitLab built from source, or container images that use a distribution’s Ruby: upgrading GitLab is not enough. Ruby itself must be 3.3.13, 3.4.12, 4.0.9 or later. Alpine’s own image ran standard Ruby and was still vulnerable after the GitLab upgrade. Users of the alpinelinux/gitlab image should pull 19.3.6 again; images pulled before October 27 and all older tags are vulnerable. Check versions on the server: # Omnibus package version sudo head -1 /opt/gitlab/version-manifest.txt dpkg -l | grep -E 'gitlab-(ce|ee)' || rpm -qa | grep -E 'gitlab-(ce|ee)' # Full environment, including Ruby, for any install type sudo gitlab-rake gitlab:env:info # Source installs and custom images: check the interpreter, not GitLab ruby -v From outside, a signed-in user can read the version at /help, and an administrator token can query GET /api/v4/version. How the attack works Rapid7 reproduced code execution on an unpatched GitLab 19.4.1 container. The demonstrated chain: The attacker needs a public project they can push to. On servers with open sign-up, that means registering an account and creating a project; Alpine’s attacker was a newly registered account. They commit a .gitlab-ci.yml that uses a regular expression in rules:exists or rules:changes, crafted so a huge repeat count is hidden from GitLab’s own validation (for example, by splitting the number with an inline regex comment), plus a file path containing four-byte UTF-8 characters. They send an unauthenticated request to the CI lint API for that project, such as GET /api/v4/projects/:id/ci/lint?content_ref=.... GitLab evaluates the configuration, Ruby recompiles the regular expression for UTF-8, the size calculation overflows, and the compiled program corrupts memory in the Puma web worker. With the right layout, the attacker gains control of execution and runs an operating system command as the GitLab user (git on Omnibus installs). When it fails, the worker crashes and Puma replaces it. The attack is noisy when it misses. Each failed attempt crashes a worker, the request runs for several seconds, the worker’s memory can jump by about 2 GiB, and the client sees a 502 error. Alpine Linux saw exactly this: eight crashes, four of which got partway through the exploit. The attacker also downloaded Alpine’s GitLab container image, most likely to tune the exploit to that exact build. A successful attempt may leave no crash and little in the web logs. Hunt for both the failures and what comes after a success. Who should act first Internet-facing GitLab with open registration and public projects. This matches the demonstrated attack path exactly. Open source projects, universities and communities that let outside contributors sign up. Any self-managed GitLab on 19.2, 19.3 or 19.4 below the fixed versions, including internal servers reachable by contractors or partners. GitLab installs that use their own Ruby, even if GitLab is already upgraded. Threat hunting playbook Hunt from October 12, 2026 (the public fix) to today. If your logs go back further, extend to October 1. Paths below are for Omnibus installs; container and Helm installs write the same logs inside the pods or to your log collector. Hunt 1: CI lint requests without a user The trigger request goes to the CI lint API. Legitimate use comes from signed-in users and tooling with tokens; anonymous requests are rare. On the server, with jq: sudo zcat -f /var/log/gitlab/gitlab-rails/api_json.log* \ | jq -c 'select((.route // "") | test("ci/lint")) | {time, remote_ip, status, duration_s, user: ."meta.user", path, ua}' \ | grep -v '"user":"[^"]' | head -100 In Splunk, if GitLab logs are collected: index=gitlab source="*api_json.log*" route="*ci/lint*" | eval user=coalesce('meta.user', "-") | stats count min(_time) as first max(_time) as last max(duration_s) as max_duration values(status) as status by remote_ip, user | sort - max_duration In OpenSearch or Elasticsearch: {"bool": { "filter": [{"wildcard": {"route": "*ci/lint*"}}], "must_not": [{"exists": {"field": "meta.user"}}]}} Prioritize anonymous lint requests that took several seconds, returned 500 or 502, or came from an address that also registered an account or pushed to a public project in the days before. Also review the GraphQL ciLint query and POST /api/v4/ci/lint if your version exposes them. Hunt 2: crashed workers and 502s on the lint route Failed exploitation crashes the Puma worker that handled the request. # Workhorse: lint requests that ended in a bad gateway sudo zcat -f /var/log/gitlab/gitlab-workhorse/current* | grep -E 'ci/lint' | grep -E '"status":502|badgateway' sudo zcat -f /var/log/gitlab/gitlab-workhorse/current* | grep 'badgateway: failed to receive response: EOF' # Puma: worker crashes and restarts, and traces through the regex matcher sudo zcat -f /var/log/gitlab/puma/puma_stderr.log* /var/log/gitlab/puma/puma_stdout.log* \ | grep -iE 'segmentation fault|SIGSEGV|regexp_matcher|worker.*(terminated|died|timed out)' # Kernel: native crashes in Ruby or Puma sudo journalctl -k --since 2026-10-12 | grep -iE 'segfault.*(ruby|puma)' GitLab’s own release notes name detections for this flaw built around the same signals: Onigmo native worker crashes, CI lint worker crashes seen by Workhorse, oversized non-ASCII paths in CI regular expressions, and bursts of scripted CI lint errors. A cluster of worker crashes this month is a lead, not noise. Hunt 3: the setup steps before the trigger The demonstrated attack needs an account and a writable public project, and both leave records. New accounts from October 12 onward, especially ones that created a public project within minutes: application_json.log and audit_json.log record user creation, and the admin area lists users by creation date. New public projects with a .gitlab-ci.yml that uses rules:exists or rules:changes with regexp, very long repeat counts, (?# inline comments, repeated [[:word:]] classes, or file paths with emoji or other four-byte characters. Pushes to those projects shortly before the lint requests in Hunt 1. Container image pulls of your GitLab image from your registry by new or unknown accounts, as in the Alpine incident. A quick Rails console check for recent public projects that touch regex rules (run on a test copy or with care in production): sudo gitlab-rails runner 'Project.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).where("created_at > ?", Time.utc(2026,10,12)).find_each { |p| puts [p.id, p.full_path, p.creator&.username, p.created_at].join(" | ") }' Hunt 4: what a successful exploit would do next Code runs as the GitLab application user, which can read the database credentials and /etc/gitlab/gitlab-secrets.json on many installs. Look for: Child processes of Puma or Ruby: sh, bash, curl, wget, python, perl, nc or base64 started by the GitLab user. With auditd, EDR or Sysmon for Linux, this is the highest-value alert you can add today. Outbound connections from the GitLab host to anything other than your mirrors, package registries, mail relay and update servers. Alpine had no outbound logging and could not prove it was clean. SSH persistence: the git user’s authorized_keys (for example /var/opt/gitlab/.ssh/authorized_keys) is managed by GitLab and every legitimate line starts with command="…gitlab-shell…". A line without that prefix is an attacker key. Files and hooks: new files under /opt/gitlab/embedded/service/gitlab-rails/public, server-side Git hooks (custom_hooks folders in Gitaly storage), and new cron entries for the git user. Account and token abuse: new administrators, personal or impersonation access tokens created outside normal patterns, new runners registered, deploy keys added, webhooks changed, and unusual API reads of CI/CD variables. Supply chain tampering: unexpected commits or tags on protected branches, release artifacts or container images published outside normal pipelines, and changes to .gitlab-ci.yml that add steps to send data out. Hold the line until you patch If you cannot upgrade today: Turn off open sign-up (Admin area, Settings, General, Sign-up restrictions) or require administrator approval. Restrict who can create and push to public projects, or change public projects to internal or private. Block unauthenticated requests to the CI lint routes at your reverse proxy or WAF: /api/v4/projects/*/ci/lint and /api/v4/ci/lint, plus the GraphQL ciLint operation where you can inspect GraphQL bodies. Alert on Hunts 1 and 2 in real time so a crash triggers a response the same hour. These steps block the demonstrated path. They leave the vulnerable regex compiler in place, so patching remains the fix. Responding to evidence of an attempt Follow Alpine Linux’s lead: when they could not rule out success, they treated the server as compromised. Preserve logs before anything rotates: Rails, Workhorse, Puma, Gitaly, Sidekiq and NGINX logs, plus the container filesystem if you run GitLab in containers. Alpine lost some attack-day logs when containers were replaced. Block the attacking accounts and addresses, and close sign-up. Upgrade to a fixed release and confirm the Ruby version for non-Omnibus installs. If anything suggests success, or you cannot prove otherwise, rebuild the server from clean media and restore data from a backup. Rotate everything GitLab holds: all user passwords, the secrets in gitlab-secrets.json (following GitLab’s documented rotation procedure), runner registration and authentication tokens, CI/CD variables, deploy keys and tokens, webhook secrets, integration credentials, push-mirror credentials and personal access tokens. Verify what GitLab produced: compare repositories against independent mirrors, check that releases and packages built during the window match their sources, and review pipelines that ran with protected variables. Tell users to change reused passwords elsewhere. Alpine observed automated logins with leaked passwords after its incident. Detection rules to keep Anonymous request to a CI lint route that runs longer than 3 seconds or returns 5xx. Puma worker crash or kernel segfault in a Ruby process on a GitLab host. Shell, interpreter or network tool started by the GitLab application user. Line in the git user’s authorized_keys without the gitlab-shell command prefix. New account that creates a public project and triggers CI lint within one hour. Frequently Asked Questions Is GitLab.com affected? No. GitLab patched GitLab.com before disclosure, and GitLab Dedicated needs no customer action. We upgraded to 19.3.6. Are we safe? Yes on official GitLab packages and images. No if your GitLab runs on a Ruby you supply, until Ruby is at 3.3.13, 3.4.12, 4.0.9 or later. Does the attacker need an account? The final trigger request is unauthenticated, but the demonstrated attack needs a public project the attacker can push to. On servers with open sign-up, anyone can arrange that. What is the CISA deadline? CISA’s KEV catalog gives federal civilian agencies until November 1, 2026 to apply the fix. Treat that as the deadline for internet-facing servers everywhere. How ThreatDefence is responding CVE-2026-97229 is in the ThreatDefence KEV feed and our vulnerability scans, so customers with GitLab in scope see it flagged with its KEV status and the fixed versions. Our SOC is identifying GitLab servers across customer environments, checking their versions, and hunting for anonymous CI lint requests, Puma worker crashes and processes started by the GitLab user where we collect those logs. If you run self-managed GitLab and want help with a retrospective hunt, contact us. Sources: GitLab patch release, CISA KEV alert, Alpine Linux incident report, Rapid7 analysis, The Hacker News.