← Back to Feed CACHED · 2026-10-23 19:05:56 · CACHE_KEY CVE-2026-89078
CVE-2026-89078 · CWE-415 · Disclosed 2026-09-24

GitLab CE/EE CI/CD regex parser double-free via crafted Onigmo pattern

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

Like handing every intern a key that lets them rewrite the blueprints for the entire factory

CVE-2026-89078 is a double-free memory corruption vulnerability in Ruby's Onigmo regular expression engine, exposed through GitLab CE/EE's CI/CD pipeline configuration parser. Any authenticated user with Developer-level access to *any* project can craft a malicious regular expression inside .gitlab-ci.yml that triggers the double-free during Regexp.new compilation. The resulting heap corruption can be weaponized into arbitrary code execution on the GitLab server process, running as the git user. Affected versions span GitLab 19.2.0–19.2.6, 19.3.0–19.3.2, and 19.4.0. The underlying Onigmo bug (PR #13332) was also patched upstream in Ruby 4.0.9, 3.4.12, and 3.3.13 on October 23, 2026. The researcher joaxcar reported this and three sibling regex-engine CVEs through HackerOne in a single month.

The vendor rates this CVSS 9.9 CRITICAL with Scope Changed (S:C), and for once the vendor score is close to reality. The PR:L (low-privilege authentication) is real friction — an external attacker does need *some* credentials — but the barrier is thin: GitLab instances routinely grant Developer access to hundreds or thousands of users, many have self-registration enabled, and federated SSO means a compromised corporate identity is sufficient. The sibling CVE-2026-97229 was later found exploitable *without* authentication at all (CVSS 10.0), and Rapid7 published a working PoC for it on October 13 demonstrating ASLR bypass and command execution. The same Onigmo code paths are involved. We downgrade only slightly from 9.9 to 9.1 because this specific CVE requires authentication and has no standalone public PoC yet — but the blast radius of a compromised GitLab server (source code, CI/CD secrets, deployment keys, registry tokens) keeps it firmly CRITICAL.

"Authenticated RCE via CI/CD regex in a canonical supply-chain component — patch now."
02 · The Attack Path

5 steps from start to impact.

STEP 01

Obtain authenticated GitLab access

The attacker needs a valid GitLab account with at least Developer-level permissions on any project. On self-managed instances, this can come from self-registration (often enabled by default), compromised SSO credentials, or an insider threat. GitLab.com users are not affected (already patched), but self-managed instances with internet exposure (~60K–86K per Shodan/Censys) are the primary target.
Conditions required:
  • Valid GitLab account with Developer role or above
  • Target runs GitLab CE/EE 19.2.0–19.4.0 (self-managed)
Where this breaks in practice:
  • Requires authentication — blocks opportunistic unauthenticated scanning
  • Organizations with strict onboarding may limit who gets Developer access
Detection/coverage: GitLab audit logs record account creation and project membership changes. Monitor for new accounts or unexpected role grants.
STEP 02

Craft malicious regex in .gitlab-ci.yml

The attacker creates or modifies a .gitlab-ci.yml file containing a specially crafted regular expression that triggers the Onigmo double-free during parsing. The regex must cause a parse failure *after an alternation* in a specific way that frees the same memory region twice. The attacker pushes this to any branch — protected branch restrictions do not prevent pipeline config from being parsed on feature branches.
Conditions required:
  • Write access to any repository branch
  • Knowledge of the Onigmo double-free trigger pattern
Where this breaks in practice:
  • No public PoC exists for this specific CVE yet
  • Heap exploitation requires understanding Onigmo/Ruby allocator internals
  • The sibling CVE-2026-97229 Rapid7 PoC demonstrates the same class of attack is achievable
Detection/coverage: GitLab's six threat-detection signatures released with the patch can identify exploitation attempts. CI/CD config audit events log .gitlab-ci.yml modifications.
STEP 03

Pipeline triggers regex compilation

When the branch is pushed, GitLab's CI/CD subsystem reads the .gitlab-ci.yml and compiles regex patterns using Ruby's Regexp.new. The crafted pattern triggers the double-free in Onigmo's C-level parser, corrupting the heap allocator's metadata. This happens server-side during pipeline validation — the attacker does not need to wait for a pipeline to actually run, just for the config to be parsed.
Conditions required:
  • CI/CD pipelines enabled on the target project (default)
  • GitLab server compiles the regex before any sandbox or validation rejects it
Where this breaks in practice:
  • Some organizations disable CI/CD on certain projects, but this is uncommon
  • The parse happens early in the pipeline lifecycle before most policy checks
Detection/coverage: Crash logs or segfaults in the Puma/Sidekiq worker processes. Monitor for unexpected Ruby process crashes with SIGSEGV/SIGABRT signals.
STEP 04

Heap corruption → arbitrary code execution

The double-free gives the attacker a use-after-free primitive on the heap. By carefully shaping heap layout through controlled allocations (e.g., additional regex patterns or string objects in the same YAML), the attacker overwrites function pointers or vtable entries to redirect execution. The Rapid7 PoC for sibling CVE-2026-97229 demonstrated ASLR bypass and command execution as the git user on the GitLab server. The same techniques apply to this double-free primitive.
Conditions required:
  • Successful heap grooming to control freed memory
  • ASLR bypass technique (demonstrated feasible by Rapid7 for sibling CVE)
Where this breaks in practice:
  • Heap exploitation is non-trivial and environment-dependent
  • Different Ruby/OS/ASLR configurations may require exploit tuning
  • No turnkey exploit exists for this specific CVE
Detection/coverage: Host-based EDR should flag anomalous child processes spawned by the Puma/Sidekiq worker. SELinux/AppArmor profiles for the git user may block post-exploitation.
STEP 05

Post-exploitation: secrets exfiltration and supply-chain pivot

With code execution as the git user on the GitLab server, the attacker can read all repository contents, CI/CD variables (including protected/masked secrets), Runner registration tokens, and database credentials from /etc/gitlab/gitlab-secrets.json. From here, the attacker can inject backdoors into any repository, compromise CI/CD pipelines to push malicious artifacts downstream, exfiltrate SSH keys and deploy tokens, and pivot to internal infrastructure via Runner networks.
Conditions required:
  • Successful code execution from step 4
  • GitLab stores secrets accessible to the git user (default architecture)
Where this breaks in practice:
  • Organizations using external secret managers (Vault, etc.) reduce the blast radius
  • Network segmentation may limit lateral movement from the GitLab host
Detection/coverage: File integrity monitoring on /etc/gitlab/gitlab-secrets.json and /var/opt/gitlab/. Network monitoring for unusual egress from the GitLab server. Git hook modifications or unexpected pushes to protected branches.
03 · Compensating Control

1
CRITICAL 9.1→IGNORE 0.0
SEVERITY REDUCED
Upgrade to GitLab 19.2.7, 19.3.3, or 19.4.1 immediately — This is the definitive fix. The patch addresses the Onigmo double-free in GitLab's bundled Ruby regex engine. Per the noisgate mitigation SLA for CRITICAL, deploy within 3 days. Single-node instances will experience brief downtime for database migrations; multi-node deployments support zero-downtime upgrades. Prioritize internet-facing instances first, then internal instances.
2
CRITICAL 9.1→HIGH 7.5
SEVERITY REDUCED
Restrict CI/CD pipeline configuration to trusted files from protected repos — Move .gitlab-ci.yml definitions to a centrally managed, access-controlled repository using GitLab's external CI/CD configuration feature. This prevents arbitrary users from injecting crafted regex patterns through pipeline configs. Deploy within 3 days as an interim control if patching is delayed.
3
CRITICAL 9.1→CRITICAL 8.5
Disable self-registration and audit Developer-level access grants — Reduce the pool of accounts that can reach the vulnerable code path. Disable public self-registration on self-managed instances, require admin approval for new accounts, and audit existing Developer+ role assignments. Remove unnecessary access. This hardens the authentication prerequisite. Deploy within 3 days.
4
CRITICAL 9.1→CRITICAL 8.8
Deploy WAF rules to inspect .gitlab-ci.yml pushes for anomalous regex patterns — If a WAF or API gateway sits in front of GitLab, add rules to flag or block git push operations containing .gitlab-ci.yml files with unusually complex or deeply nested regex patterns. This is a best-effort heuristic — evasion is likely — but it raises the cost of exploitation. Deploy within 3 days.
5
CRITICAL 9.1→HIGH 7.0
SEVERITY REDUCED
Network-segment GitLab servers and restrict egress — Place GitLab servers in a dedicated network segment with strict egress filtering. Block outbound connections except to known registries, Runners, and monitoring endpoints. This limits post-exploitation lateral movement and data exfiltration even if RCE is achieved. Deploy within 3 days.
6
CRITICAL 9.1→CRITICAL 9.0
Monitor for exploitation signatures and anomalous process behavior — Deploy GitLab's six released threat-detection signatures on self-managed instances. Configure host EDR to alert on child processes spawned by Puma/Sidekiq workers (e.g., /bin/sh, curl, wget). Monitor for SIGSEGV/SIGABRT crashes in Ruby processes. Deploy within 3 days.
What doesn't work
  • Rate limiting or IP blocking — the exploit is a single crafted git push, not a brute-force attack. Rate limiting will not prevent a one-shot exploitation attempt from an authenticated user.
  • Protected branches alone — GitLab parses .gitlab-ci.yml from *any* branch push, not just merges to protected branches. The regex compilation happens during pipeline validation on feature branches before any merge request approval.
  • GitLab's built-in CI/CD linting — the linter validates YAML structure and keyword syntax but does not sanitize regex patterns passed to Regexp.new. The malicious pattern is syntactically valid YAML.
  • Ruby-level regex timeout (Regexp.timeout) — the double-free triggers during *compilation* (parsing), not during *matching*. Regex timeout settings only apply to match execution and do not prevent the memory corruption.
04 · Intelligence Metadata

The supporting signals.

In-the-Wild ExploitationNot confirmed for CVE-2026-89078 specifically. However, the sibling CVE-2026-97229 (same Onigmo regex engine, integer overflow variant) has active exploitation confirmed within 24 hours of disclosure. CVE-2026-110264 (unauthenticated variant, CVSS 10.0) also has confirmed in-the-wild exploitation per InfoQ.
Proof-of-ConceptNo public PoC for CVE-2026-89078 specifically. However, Rapid7 published a full PoC for sibling CVE-2026-97229 on Oct 13, 2026, demonstrating ASLR bypass and command execution as the git user. An OppositDeer PoC exists for CVE-2026-110264. The underlying Onigmo fix is tracked in PR #13332. No repos named CVE-2026-89078 found on GitHub or pocindex.io as of Oct 24, 2026.
EPSS0.0044 (low, ~top 10% percentile). This reflects the authentication requirement and lack of standalone PoC. Expect this to rise as the Rapid7 sibling PoC lowers the bar for adaptation.
CISA KEVNot listed. The sibling CVE-2026-110264 (unauthenticated, CVSS 10.0) is the more likely KEV candidate if CISA acts on this family.
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L — Network-accessible, low complexity, low privilege required, no user interaction, Scope Changed (compromise extends beyond GitLab process to underlying server and downstream systems). The S:C is earned — code execution on a GitLab server directly exposes secrets, repos, and CI/CD infrastructure.
Affected VersionsGitLab CE/EE 19.2.0–19.2.6, 19.3.0–19.3.2, 19.4.0. The underlying Onigmo bug affects Ruby ≤4.0.8, ≤3.4.11, ≤3.3.12 and all unsupported Ruby series (3.2 and older receive no patches).
Fixed VersionsGitLab 19.2.7, 19.3.3, 19.4.1 (Sept 23, 2026). Ruby 4.0.9, 3.4.12, 3.3.13 (Oct 23, 2026). A subsequent release (19.4.2, 19.3.4, 19.2.8) also includes these fixes. Note: Ruby 3.2 and older are EOL — no backport available.
Exposure SurfaceShodan shows ~60,000 internet-facing GitLab self-managed instances; Censys reports ~86,000. Top countries: China (19K), US (13K), Germany (12K), Russia (8.6K), France (5K). GitLab.com (SaaS) was patched by GitLab Inc. before public disclosure.
Researcherjoaxcar via HackerOne. Reported four Onigmo regex-engine CVEs in GitLab within a single month (CVE-2026-89078, CVE-2026-93577, CVE-2026-97229, CVE-2026-110264).
Vendor Detection SignaturesGitLab released six threat-detection signatures with the Sept 23 patch to identify exploitation attempts on self-managed instances. No Nuclei template confirmed for this specific CVE as of Oct 24, 2026.

Sources.

  1. GitLab Critical Patch Release 19.4.1, 19.3.3, 19.2.7
  2. Ruby Onigmo Regexp Fixes for GitLab CVEs (ruby-lang.org)
  3. Strix.ai CVE-2026-89078 Detail
  4. CiberSafety — Critical RCE in CI/CD, CVE-2026-89078/93577
  5. nflo.tech — CVE-2026-93577, CVE-2026-89078 Analysis
  6. Safeguard.sh — Ruby Onigmo Fixes Behind Four GitLab RCE CVEs
  7. CERT-EU Advisory 2026-016 — Critical GitLab Vulnerability
  8. Daily Security Review — Public Exploit for CVE-2026-97229
05 · The Call

Final Verdict
= UNCHANGED to CRITICAL (9.1/10)

Why this verdict

  • Authentication friction (−0.4): PR:L requires a valid GitLab Developer account. This blocks drive-by unauthenticated scanning and narrows the attacker pool — but the barrier is thin. GitLab instances routinely have hundreds to thousands of Developer-level users, self-registration is often enabled by default, and federated SSO means any compromised corporate identity suffices. Downward pressure is real but modest.
  • No standalone PoC (−0.3): No public exploit exists for CVE-2026-89078 specifically. However, the Rapid7 PoC for sibling CVE-2026-97229 demonstrates the full chain (ASLR bypass, code execution as git user) in the *same Onigmo regex engine*, making adaptation to the double-free primitive a matter of exploit development rather than novel research. This gap is closing.
  • Heap exploitation complexity (−0.1): Double-free → RCE requires heap grooming and ASLR bypass. This is non-trivial but well-studied, and the sibling PoC proves it's achievable in this specific Ruby/Onigmo environment. Minimal additional friction.
  • Role multiplier: GitLab is a canonical high-value-role component. GitLab is by definition a CI/CD platform, source code repository, and secrets store — hitting three entries in the high-value catalog simultaneously (CI/CD + artifact stores, supply-chain registries, and effectively a secret manager via CI/CD variables). The blast radius of a compromised GitLab server is supply-chain scale: the attacker gains access to all source code, CI/CD secrets, deployment keys, Runner tokens, and the ability to inject malicious code into every downstream build artifact. 100% of GitLab installations occupy this high-value role by definition. The chain documented here (auth → crafted regex → RCE as git user → secret exfiltration → supply-chain pivot) succeeds in every deployment role from dev sandbox to enterprise production. Verdict floor: CRITICAL.
  • Sibling exploitation pressure: Four CVEs in the same Onigmo code path within one month, escalating from authenticated (this CVE) to unauthenticated (CVE-2026-110264, CVSS 10.0) with active in-the-wild exploitation. This family is under active attacker research and the authenticated requirement may be the only thing keeping *this specific* CVE from mass exploitation today.

Why not higher?

The vendor's 9.9 assumes trivial exploitation at low complexity. In practice, the authentication requirement meaningfully reduces the externally-reachable attack surface compared to the unauthenticated sibling CVE-2026-110264 (CVSS 10.0), and no standalone PoC exists for the double-free variant yet. The heap exploitation step, while demonstrated feasible for the sibling integer overflow, still requires adaptation work. These factors justify a modest 0.8-point reduction from the vendor score rather than a full match.

Why not lower?

GitLab is a canonical supply-chain component where code execution directly translates to fleet-scale impact — source code theft, secret exfiltration, pipeline poisoning, and downstream artifact compromise. The authentication barrier is thin (Developer access is widely granted), a working PoC for the sibling CVE in the same code path proves the exploitation chain is practical, and ~60K–86K self-managed instances are internet-facing. Dropping below CRITICAL would require evidence that the double-free primitive is fundamentally unexploitable or that the vast majority of GitLab instances have compensating controls that prevent CI/CD config parsing — neither is true.

06 · Verification

Crowdsourced verification payload.

Run this script on each self-managed GitLab server (Omnibus or source install) as root or a user with read access to /opt/gitlab/ or the GitLab installation directory. Example: sudo bash check_cve_2026_89078.sh. No network access required.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check_cve_2026_89078.sh — Detect CVE-2026-89078 (GitLab Onigmo double-free)
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -euo pipefail

VERSION_FILE="/opt/gitlab/version-manifest.txt"
VERSION_FILE_ALT="/opt/gitlab/VERSION"
GL_VERSION=""

# Try Omnibus version file first
if [[ -f "$VERSION_FILE_ALT" ]]; then
  GL_VERSION=$(cat "$VERSION_FILE_ALT" | head -1 | tr -d '[:space:]')
elif command -v gitlab-rake &>/dev/null; then
  GL_VERSION=$(gitlab-rake gitlab:env:info 2>/dev/null | grep -i 'Version:' | head -1 | awk '{print $2}' | tr -d '[:space:]')
elif [[ -f "/home/git/gitlab/VERSION" ]]; then
  GL_VERSION=$(cat /home/git/gitlab/VERSION | head -1 | tr -d '[:space:]')
fi

if [[ -z "$GL_VERSION" ]]; then
  echo "UNKNOWN — Could not determine GitLab version. Is this a GitLab server?"
  exit 2
fi

echo "Detected GitLab version: $GL_VERSION"

# Parse major.minor.patch
IFS='.' read -r MAJOR MINOR PATCH <<< "$(echo "$GL_VERSION" | grep -oE '^[0-9]+\.[0-9]+\.[0-9]+')"

if [[ -z "$MAJOR" || -z "$MINOR" || -z "$PATCH" ]]; then
  echo "UNKNOWN — Could not parse version string: $GL_VERSION"
  exit 2
fi

# Affected: 19.2.0-19.2.6, 19.3.0-19.3.2, 19.4.0
# Fixed:    19.2.7+,      19.3.3+,      19.4.1+
if (( MAJOR < 19 )); then
  echo "PATCHED — Version $GL_VERSION is below the affected range (19.2–19.4)."
  exit 0
elif (( MAJOR > 19 )); then
  echo "PATCHED — Version $GL_VERSION is above the affected range."
  exit 0
elif (( MAJOR == 19 )); then
  if (( MINOR < 2 )); then
    echo "PATCHED — Version $GL_VERSION is below the affected range (19.2+)."
    exit 0
  elif (( MINOR == 2 )); then
    if (( PATCH >= 7 )); then
      echo "PATCHED — Version $GL_VERSION includes the fix (19.2.7+)."
      exit 0
    else
      echo "VULNERABLE — Version $GL_VERSION is affected (19.2.0–19.2.6). Upgrade to 19.2.7+."
      exit 1
    fi
  elif (( MINOR == 3 )); then
    if (( PATCH >= 3 )); then
      echo "PATCHED — Version $GL_VERSION includes the fix (19.3.3+)."
      exit 0
    else
      echo "VULNERABLE — Version $GL_VERSION is affected (19.3.0–19.3.2). Upgrade to 19.3.3+."
      exit 1
    fi
  elif (( MINOR == 4 )); then
    if (( PATCH >= 1 )); then
      echo "PATCHED — Version $GL_VERSION includes the fix (19.4.1+)."
      exit 0
    else
      echo "VULNERABLE — Version $GL_VERSION is affected (19.4.0). Upgrade to 19.4.1+."
      exit 1
    fi
  elif (( MINOR > 4 )); then
    echo "PATCHED — Version $GL_VERSION is above the affected minor range."
    exit 0
  fi
fi

echo "UNKNOWN — Could not determine vulnerability status for $GL_VERSION."
exit 2