Someone left a loaded gun in the regex compiler and 25,000 GitLab servers are standing in front of it
CVE-2026-97229 is an integer overflow (CWE-190) in the Onigmo regular-expression compiler used by GitLab's CI/CD pipeline engine. A crafted regex in a .gitlab-ci.yml file passes initial US-ASCII validation, but when the CI worker re-evaluates it against a UTF-8 repository path the compiler's program-size calculation wraps, producing invalid VM instructions. Control-flow integrity is lost and the attacker lands arbitrary OS command execution as the git user — the identity that owns every repository, CI variable, deploy key, and runner token on the box. Affected versions span GitLab CE/EE 19.2.0–19.2.7, 19.3.0–19.3.4, and 19.4.0–19.4.1. Patched releases are 19.2.8, 19.3.5, and 19.4.2, shipped October 12 2026.
GitLab scored this a perfect CVSS 10.0 with vector AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H. The vendor severity is almost exactly right. The one nuance is that the 'no privileges required' claim assumes open self-registration, which is GitLab's *default* for self-managed instances but not universal. On locked-down instances where registration is disabled and project creation is restricted, the chain requires an existing low-privilege account — making the realistic vector closer to PR:L (CVSS ~9.9). That distinction costs the vendor score 0.2 points at most. The blast radius — source code, secrets, runners, connected cloud accounts, downstream supply chain — justifies keeping this firmly in CRITICAL territory.
5 steps from start to impact.
Self-register on target GitLab instance
signup_enabled: true). No CAPTCHA or admin approval is required in the default configuration. This gives the attacker an authenticated session in under 30 seconds.- Internet-facing self-managed GitLab instance
- Self-registration enabled (default)
- Enterprises that disabled self-registration or require admin approval block this step entirely
- Instances behind VPN/SSO gating are unreachable
users.created audit events for unusual sign-up patterns; GreyNoise tags for GitLab reconnaissance scans.Create a public project with UTF-8 path
- Authenticated session from Step 1
- Public project creation not restricted
- Organizations that restrict project visibility to Internal/Private block the public-project requirement
- Namespace-level restrictions on project creation can limit this
projects.created events.Commit malicious .gitlab-ci.yml with crafted regex
.gitlab-ci.yml containing a specially constructed regular expression designed to pass GitLab's ASCII-mode validation but overflow the Onigmo compiler's integer size calculation when recompiled for UTF-8. The regex pattern is compact — a few hundred bytes — and uses legal regex syntax that evades naive static analysis.- Write access to the project (satisfied by project ownership from Step 2)
- CI/CD enabled for the project (default)
- Push rules or pre-receive hooks that reject
.gitlab-ci.ymlchanges from non-maintainers would block this, but the attacker owns the project - CI lint validation does not catch the malicious pattern because it compiles cleanly in ASCII mode
.gitlab-ci.yml changes; regex-complexity scanners (not standard in GitLab).Trigger CI/CD pipeline evaluation
.gitlab-ci.yml. The regex is compiled by the Onigmo engine in the Ruby worker process. Upon encountering the UTF-8 repository path, Onigmo re-enters its compiler with the new encoding, and the integer overflow in the program-size calculation produces a truncated bytecode buffer. This is fully automated — no additional attacker interaction is needed after the push.- CI/CD runners or Sidekiq workers are processing jobs (default active state)
- The pipeline is not blocked by manual approval gates
- Instances with CI/CD disabled at the instance level are not vulnerable
- Protected-branch rules do not apply — the attacker's own branch triggers the pipeline
Heap corruption → arbitrary code execution as git user
git user. Rapid7's PoC demonstrates this works reliably on GitLab CE 19.4.1 with ASLR enabled. The git user has read/write access to all repositories, CI/CD secrets, runner registration tokens, and deploy keys on the instance.- Steps 1–4 completed successfully
- Target is a Linux amd64 self-managed instance (PoC confirmed on this platform)
- SELinux in enforcing mode may constrain post-exploitation lateral movement
- Container-isolated GitLab deployments (Kubernetes Helm chart) limit blast radius to the container, though secrets and repo data are still accessible
git user; auditd rules for execve from gitlab-rails worker PIDs; network-level detection of reverse shells or unusual egress from the GitLab host.gitlab_rails['gitlab_signup_enabled'] = false in gitlab.rb and run gitlab-ctl reconfigure. This breaks Step 1 of the attack chain by preventing unauthenticated attackers from obtaining the account needed to create projects and push code. Deploy within 3 days per the noisgate mitigation SLA for CRITICAL findings. Existing authenticated insiders could still exploit this, so pair with project-creation restrictions.gitlab-secrets.json before upgrading.execve calls from the git user, monitor Sidekiq worker crash rates, alert on new SSH keys or personal access tokens created by recently registered accounts, and watch for reverse-shell egress patterns from the GitLab host. This does not prevent exploitation but dramatically shortens dwell time.- WAF regex-payload rules — the malicious regex is embedded in a
.gitlab-ci.ymlfile committed via Git push (SSH or HTTPS), not in an HTTP request parameter. Standard WAF signatures cannot inspect Git pack data. - Rate limiting on the CI lint API — the exploit does not use the lint endpoint directly; it triggers compilation via a normal
git pushthat activates the CI pipeline. Rate-limiting API calls has no effect on this path. - MFA enforcement — the attacker creates their own account and enrolls their own MFA device. MFA prevents account takeover, not self-registration abuse.
- Container image scanning — the vulnerability is in GitLab's Ruby application layer, not in a base image dependency. Standard container vulnerability scanners will not flag it unless they specifically check the GitLab application version.
The supporting signals.
| In-the-wild exploitation | No confirmed exploitation as of 2026-10-16. Neither GitLab, CISA, nor Censys have observed active campaigns. GreyNoise has not published a dedicated tag. SOCRadar and Feedly both report 'no confirmed attribution' with low confidence. |
|---|---|
| Proof-of-concept availability | Public PoC exists. Rapid7 published jburgess-r7/CVE-2026-97229-PoC on GitHub — validated against GitLab CE 19.4.1 on Linux amd64 with ASLR. A second repo AmpedWasTaken/CVE-2026-97229-GitLab-CI-CD-Regex-Compiler-RCE is indexed on Sploitus. Both demonstrate full RCE. |
| EPSS score | 0.00547 (0.547%) — low exploitation probability per the FIRST model, placing this in roughly the 80th percentile. This reflects the recency of disclosure (3 days) and absence of ITW activity; expect this to climb as scanners propagate. |
| CISA KEV status | Not listed in the Known Exploited Vulnerabilities catalog as of 2026-10-16. |
| CVSS vector interpretation | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H — Network-attackable, low complexity, no privileges, no user interaction, scope change (escapes the regex sandbox into the OS), full CIA impact. The PR:N is accurate for default-config self-managed instances with open registration. |
| Affected version ranges | GitLab CE/EE 19.2.0–19.2.7, 19.3.0–19.3.4, 19.4.0–19.4.1. Versions before 19.2.0 are not affected — the vulnerable regex compiler code path was introduced in the 19.2 release. |
| Fixed versions | 19.2.8, 19.3.5, 19.4.2 — released October 12, 2026. GitLab.com was patched same day. GitLab Dedicated environments patched automatically. |
| Internet exposure | Censys identifies 25,766 internet-facing hosts running vulnerable versions out of 82,454 total detected GitLab instances (~31% unpatched). Geographic concentration: Germany (6,469), Russia (4,173), United States (2,927). |
| Disclosure timeline | Reported by Johan Carlsson (joaxcar) via GitLab's HackerOne bug bounty program. Patch released 2026-10-12; CVE published to NVD 2026-10-13; Rapid7 PoC published 2026-10-13. |
| Reporter | Johan Carlsson (joaxcar) — independent security researcher, HackerOne contributor. |
Sources.
- GitLab Critical Patch Release 19.4.2, 19.3.5, 19.2.8
- Rapid7 ETR: CVE-2026-97229 Unauthenticated RCE in GitLab
- SecurityOnline: Public PoC Disclosed for CVSS 10.0 GitLab RCE
- Help Net Security: GitLab admins urged to patch critical RCE flaw
- SOCRadar: CVE-2026-97229 Critical GitLab RCE
- The Hacker News: GitLab Patches CVSS 10.0 Flaw
- CERT-EU Advisory: Critical Vulnerability in GitLab CE/EE
- Feedly CVE Intelligence: CVE-2026-97229
Why this verdict
- Self-registration prerequisite (−0.2): The CVSS vector claims PR:N, but the chain requires creating an account and a project. On default-config self-managed GitLab this is trivially automatable, but instances with registration disabled or admin-approval gates are shielded. This narrows the truly zero-auth population by an estimated 15–25% of enterprise deployments, warranting a minor deduction from the perfect 10.
- Self-managed only scope: GitLab.com (the largest single deployment) and GitLab Dedicated are already patched. The vulnerability only threatens self-managed instances, which is the majority of enterprise deployments but excludes the SaaS population entirely.
- No ITW exploitation yet: Despite a public PoC since October 13, no confirmed exploitation has been observed in 3 days. EPSS is 0.547%. This does *not* lower the ceiling — it just means the window for proactive patching is still open.
- Role multiplier: CI/CD and supply-chain platform (CRITICAL floor enforced). GitLab is a *canonical* high-value-role component — it is literally named in the CI/CD + artifact stores catalog. ≥90% of GitLab self-managed installations serve as the primary source code repository and CI/CD engine for their organization. A compromised GitLab instance gives the attacker: (1) read/write to all source code, (2) all CI/CD secrets and environment variables, (3) runner registration tokens for lateral movement into build infrastructure, (4) deploy keys for production environments, (5) supply-chain injection capability by modifying any pipeline. Blast radius is supply-chain-scale. This floors the verdict at CRITICAL regardless of other friction.
- PoC maturity is high: Rapid7's published exploit works with ASLR enabled on the current release. The attack is fully automatable — from registration to shell in a single script. The gap between 'PoC published' and 'mass scanning begins' is historically 3–7 days for GitLab vulnerabilities (cf. CVE-2023-7028 timeline).
Why not higher?
The vendor already assigned the maximum possible score of 10.0. The only conceivable 'higher' would be if we invented a score above 10. We deducted 0.2 because the attack is not truly zero-privilege — self-registration is a prerequisite that some fraction of deployments have disabled. A CVSS 9.8 is still the highest score possible for a PR:L vector with otherwise identical parameters, and CRITICAL is the highest severity bucket.
Why not lower?
Downgrading below CRITICAL would require evidence that either (a) the high-value-role floor does not apply, or (b) the attack chain has a hard prerequisite that fails in >99% of deployments. Neither condition is met. GitLab self-managed is definitionally a CI/CD platform — the high-value-role floor applies to effectively 100% of installs. The self-registration prerequisite is satisfied by default configuration. The PoC is public, functional, and ASLR-resistant. Reducing to HIGH would misrepresent the blast radius of a supply-chain-scale compromise and leave defenders under-prioritizing a vulnerability that is days away from mass exploitation.
Crowdsourced verification payload.
Run this script on each self-managed GitLab host as root or a user with read access to /opt/gitlab/. No arguments needed. Invoke with: sudo bash check_cve_2026_97229.sh. Exits 1 for VULNERABLE, 0 for PATCHED, 2 for UNKNOWN.
#!/bin/bash
# check_cve_2026_97229.sh — Detect CVE-2026-97229 (GitLab CI/CD regex RCE)
# Run on the target GitLab host as root.
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -euo pipefail
VERSION_FILE="/opt/gitlab/embedded/service/gitlab-rails/VERSION"
if [[ ! -f "$VERSION_FILE" ]]; then
# Try Docker / Kubernetes path
VERSION_FILE="/opt/gitlab/embedded/service/gitlab-rails/VERSION"
if command -v gitlab-rake &>/dev/null; then
RAW_VERSION=$(gitlab-rake gitlab:env:info 2>/dev/null | grep -oP 'GitLab:\s+\K[0-9]+\.[0-9]+\.[0-9]+')
else
echo "UNKNOWN — cannot determine GitLab version. Is this a GitLab host?"
exit 2
fi
else
RAW_VERSION=$(cat "$VERSION_FILE" 2>/dev/null)
fi
if [[ -z "${RAW_VERSION:-}" ]]; then
echo "UNKNOWN — could not read GitLab version."
exit 2
fi
echo "Detected GitLab version: $RAW_VERSION"
# Parse major.minor.patch
IFS='.' read -r MAJOR MINOR PATCH <<< "$RAW_VERSION"
# Only versions 19.2.x through 19.4.x are affected
if (( MAJOR != 19 )); then
echo "PATCHED — major version $MAJOR is outside the affected range (19.2–19.4)."
exit 0
fi
if (( MINOR < 2 )); then
echo "PATCHED — version $RAW_VERSION predates the vulnerable code (introduced in 19.2.0)."
exit 0
fi
if (( MINOR > 4 )); then
echo "PATCHED — version $RAW_VERSION is newer than all affected branches."
exit 0
fi
# Check each affected branch
case "$MINOR" in
2)
if (( PATCH >= 8 )); then
echo "PATCHED — $RAW_VERSION includes the fix (19.2.8+)."
exit 0
else
echo "VULNERABLE — $RAW_VERSION is affected. Upgrade to 19.2.8 or later."
exit 1
fi
;;
3)
if (( PATCH >= 5 )); then
echo "PATCHED — $RAW_VERSION includes the fix (19.3.5+)."
exit 0
else
echo "VULNERABLE — $RAW_VERSION is affected. Upgrade to 19.3.5 or later."
exit 1
fi
;;
4)
if (( PATCH >= 2 )); then
echo "PATCHED — $RAW_VERSION includes the fix (19.4.2+)."
exit 0
else
echo "VULNERABLE — $RAW_VERSION is affected. Upgrade to 19.4.2 or later."
exit 1
fi
;;
*)
echo "UNKNOWN — version $RAW_VERSION is on an unrecognized minor branch."
exit 2
;;
esac