Somebody left `Regexp.new()` pointed at raw user input for twelve years, and now one Org-mode keyword topples the entire source-code vault
CVE-2026-110264 is an integer overflow in Onigmo, the Ruby regular-expression engine that ships inside every GitLab CE/EE self-managed package. The org-ruby gem — responsible for rendering Emacs Org-mode (.org) files in the GitLab web UI — passes raw #+TODO: keywords straight into Regexp.new(). An attacker who crafts a keyword containing more than 32,767 generic repeats causes Onigmo's signed 16-bit repeat-ID counter to wrap, producing an out-of-bounds heap write during regex compilation. That write can be steered into a system() call executing as the git user, giving the attacker a shell on the GitLab host. Every self-managed version from 7.0 (released June 2014) through 19.5.0 is affected — roughly 12 years of release branches. Fixes landed on 2026-10-21 in 19.5.1, 19.4.3, 19.3.6, 19.2.9, 19.1.9, 19.0.10, and 18.11.13. GitLab.com and GitLab Dedicated are already patched and not at risk.
GitLab's CNA-assigned CVSS 10.0 (AV:N/AC:L/PR:N/UI:N/S:C) is aggressive but largely defensible for the self-managed population. The PR:N component is realistic because most self-managed instances historically default to open registration (admin-approval gating was added later and is frequently not enforced), meaning an attacker can create an account, push a malicious .org file to a new public project, and trigger the rendering themselves — no victim interaction required. The only meaningful friction is that some hardened enterprises close registration entirely or restrict the instance to VPN-only access, turning this into an authenticated/internal-network bug for those deployments. That is not enough to pull a CI/CD platform below CRITICAL. The sibling CVE-2026-97229, patched just nine days earlier on Oct 12 via a nearly identical integer-overflow in the CI/CD regex path, proved the exploit class is viable — and the Oct 12 patches are *below* the new fixed versions, so instances that patched for -97229 are still exposed to -110264.
5 steps from start to impact.
Identify exposed GitLab self-managed instance
/help or /users/sign_in page, which embeds the GitLab version string in the HTML footer.- Target runs GitLab CE/EE self-managed (not GitLab.com or Dedicated)
- Instance is reachable over the network
- Enterprises behind VPN/ZTNA reduce the reachable population to internal users only
Obtain commit access via open registration
- Open registration enabled, OR existing/compromised credentials
- Locked-down instances with closed registration block this step entirely
- Admin-approval gating adds a delay; attacker must wait for approval
- Instances restricted to SAML/LDAP-only auth raise the bar significantly
user_created and project_created events; anomalous registration spikesCommit malicious .org file with crafted #+TODO: keyword
.org file containing a #+TODO: line with >32,767 generic regex repeats. The payload is small (a few KB) and looks like a benign text file to casual inspection. Naming the file README.org causes GitLab to auto-render it on the project landing page, but any .org file path works when navigated to directly. No CI/CD pipeline configuration is needed — this is purely a file-rendering exploit.- Ability to
git pushto any branch of any project on the instance
- Push rules blocking
.orgextensions would stop this, but this is not a standard configuration - Server-side Git hooks could theoretically inspect file content, but no detection signature exists pre-patch
#+TODO: patterns (not standard)Trigger Org-mode rendering → Onigmo integer overflow → OOB write
README.org). GitLab's backend calls the org-ruby gem to render the file. The gem passes the #+TODO: keyword into Regexp.new(), triggering Onigmo's regex compiler. The >32,767 repeats overflow the signed 16-bit repeat-ID, producing invalid regex bytecode that causes an out-of-bounds heap write when matched. The attacker controls both payload delivery and trigger — no victim interaction is required.- GitLab web UI is reachable (standard HTTPS)
- org-ruby gem is present (bundled in all GitLab CE/EE since 7.0)
- If a reverse proxy strips or blocks requests to
.orgfile render endpoints, the trigger fails — but this is an unusual and fragile configuration - ASLR and stack canaries on the GitLab host may reduce exploit reliability on hardened Linux kernels
Regexp compilation errors in production.logCode execution as git user → full GitLab compromise
system() call, granting the attacker a shell as the git user. On Omnibus installations, git has read access to the entire GitLab data directory: repository contents, CI/CD secrets in the database, runner registration tokens, SSH keys, and Rails secret key material. From here the attacker can dump every repository, inject backdoors into source code, extract CI/CD variables (cloud credentials, deploy keys), forge session cookies via the Rails secret, and pivot to connected infrastructure including CI runners, container registries, and deployment targets.- OOB write is successfully weaponized into controlled code execution
- Memory corruption reliability is architecture-dependent; binary hardening (PIE, RELRO, stack canaries) complicates exploitation but does not prevent it for skilled attackers
- SELinux/AppArmor in enforcing mode on the GitLab host may limit post-exploitation lateral movement
git user process trees; unexpected outbound connections from the GitLab hostgit user and anomalous outbound connections from the GitLab host..org file rendering at the reverse proxy layer — If you front GitLab with nginx, HAProxy, or a CDN, add a rule that returns HTTP 403 for requests matching repository file-view paths ending in .org (e.g., ~* /.*/-/blob/.*\.org$). This is fragile — it may break legitimate Org-mode file viewing and may not cover all rendering paths (raw endpoints, API, merge request diffs) — but it raises the bar for the specific documented attack vector while you schedule patching. Test thoroughly before deploying.- WAF rules on HTTP parameters: The payload is delivered via
git push(Git pack protocol over HTTPS or SSH), not as an HTTP form/query parameter. Web application firewalls cannot inspect the contents of Git pack data for malicious Org-mode keywords. - The Oct 12 patch for CVE-2026-97229: That patch (19.4.2, 19.3.5, 19.2.8) fixes the CI/CD regex path only. Those version numbers are *below* the -110264 fixed versions. If you patched on Oct 12 and stopped, you are still vulnerable to this CVE.
- Disabling CI/CD pipelines or restricting Runner access: CVE-2026-110264 exploits the Org-mode *file rendering* path, not CI/CD pipeline execution. Disabling pipelines has no effect on this vulnerability.
- IP allowlisting on the GitLab API alone: The attack uses
git pushfor payload delivery and standard web UI navigation for triggering. API restrictions do not cover these paths.
The supporting signals.
| In-the-wild exploitation | None observed. GitLab confirms no known exploitation as of Oct 21, 2026. Not listed in CISA KEV. However, the sibling CVE-2026-97229 (same integer-overflow class, CI/CD regex path) saw PoC publication and active probing within days of its Oct 12 disclosure, establishing a clear pattern for this bug family. |
|---|---|
| PoC availability | No public PoC as of Oct 22. pocindex.io returns no results. No GitHub repos named after CVE-2026-110264 found. The sibling CVE-2026-97229 has a PoC on vulners.com demonstrating the integer-overflow pattern. Given that GitLab's advisory and the hol.org writeup disclose the precise trigger (#+TODO: keyword, >32,767 repeats, Regexp.new()), a working PoC is likely days away from public release. |
| EPSS | Not yet scored — CVE was published Oct 21, 2026; FIRST EPSS data typically requires 24–48 hours. Given the CVSS 10.0 network vector and GitLab's exploitation history (multiple prior CVSS 10 bugs exploited within a week), expect a high EPSS probability once scored. |
| KEV status | Not listed in CISA Known Exploited Vulnerabilities catalog as of Oct 22, 2026. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H — 10.0 Critical per GitLab CNA advisory. Network-reachable, low complexity, no privileges, no user interaction, scope change (compromise of the GitLab host impacts all hosted projects and connected infrastructure), full confidentiality/integrity/availability impact. |
| Affected versions | GitLab CE/EE self-managed 7.0 through 19.5.0 — every release from June 2014 to Oct 2026. The underlying flaw exists in Onigmo (Ruby's regex engine) as bundled by GitLab. |
| Fixed versions | 18.11.13, 19.0.10, 19.1.9, 19.2.9, 19.3.6, 19.4.3, 19.5.1 — all released Oct 21, 2026. Branches older than 18.11 have no backport; those instances must upgrade to 18.11.13+ or a supported 19.x branch. Note: the Oct 12 patches for CVE-2026-97229 (19.4.2, 19.3.5, 19.2.8) are *below* these versions and do NOT protect against -110264. |
| Internet exposure | Censys indexes ~86,000 GitLab hosts with ~208,000 web properties globally. Top countries: China (19,435), US (13,242), Germany (12,249), Russia (8,639), France (5,062). Prior GitLab critical CVEs showed 13,700+ instances still unpatched weeks after disclosure. Expect similar lag here. |
| Disclosure timeline | Oct 12, 2026: sibling CVE-2026-97229 patched (19.4.2, 19.3.5, 19.2.8). Oct 20: GitLab publishes Sigma detections. Oct 21: CVE-2026-110264 patches released. Oct 22: 1 day post-patch; vast majority of self-managed instances are still unpatched. |
| Researcher / reporter | joaxcar via GitLab's HackerOne bug bounty program. No public writeup from the researcher yet. |
Sources.
- GitLab Critical Patch Release 19.5.1 (vendor advisory)
- The Hacker News — Critical GitLab Flaw (CVSS 10.0)
- hol.org — CVE-2026-110264 campaign analysis
- Rapid7 — GitLab CVSS 10.0 authentication catch (sibling CVE-2026-97229)
- vulners.com — PoC for GitLab Onigmo integer overflow (CVE-2026-97229)
- Cybernews — Thousands of vulnerable GitLab servers exposed
- netmanageit — CVE-2026-110264 coverage
- GitLab open registration default issue
Why this verdict
- Unauthenticated RCE with attacker-controlled trigger: The attacker delivers the payload (malicious
.orgfile) and triggers exploitation (navigating to the file URL) without requiring any victim interaction. On instances with open registration — still the historical default — the full chain is achievable by an anonymous internet attacker. This satisfies CVSS PR:N/UI:N for the majority of the installed base. - Twelve-year affected window, massive exposed population: Every GitLab CE/EE release from 7.0 (2014) through 19.5.0 is vulnerable. Censys shows ~86,000 exposed instances. The patch is 1 day old; expect >90% of the internet-facing population to remain unpatched for weeks, consistent with prior GitLab critical CVE patching velocity.
- Sibling CVE proves the exploit class is weaponizable: CVE-2026-97229 — same integer-overflow root cause in the same regex engine, patched just 9 days earlier — already has a public PoC and saw active probing. This dramatically compresses the timeline to weaponization for -110264. The technical trigger (
#+TODO:keyword, >32,767 repeats) is fully documented in public sources. - No vendor workaround available: GitLab's advisory provides no interim mitigation. There is no supported method to disable Org-mode rendering. Administrators cannot reduce their exposure without patching or applying non-standard compensating controls.
- Role multiplier — GitLab IS the CI/CD and SCM platform: This is not software that occasionally occupies a high-value role; GitLab *is* the supply-chain infrastructure by definition. ≥95% of GitLab installations serve as the primary source code repository and CI/CD engine for their organization. Compromise of the GitLab host yields: (1) supply-chain-scale impact — the attacker can inject backdoors into any repository's source code and CI/CD pipelines, propagating malicious code to all downstream builds and deployments; (2) credential-scale impact — CI/CD variables, deploy keys, runner tokens, Rails secret keys, and SSH keys are all accessible to the
gituser; (3) identity-scale impact — forging sessions via Rails secrets grants admin access to every GitLab account. This blast radius is categorically domain/fleet/supply-chain tier. The verdict floor for a canonical CI/CD platform with a viable unauthenticated RCE chain is CRITICAL. - 0.4-point reduction from the theoretical 10.0: (a) Achieving reliable heap-corruption-to-code-execution across 12 years of diverse binary builds requires non-trivial exploit engineering — this is not a simple command injection; (b) the subset of instances with closed registration and VPN-only access genuinely reduces the reachable population for those deployments; (c) no confirmed in-the-wild exploitation yet. These frictions prevent a full 10.0 but do not pull below CRITICAL given the role multiplier and blast radius.
Why not higher?
A 10.0 would require truly zero-interaction, universally reliable exploitation — like a wormable network service bug. CVE-2026-110264 requires the attacker to commit a file and trigger rendering (self-serviceable but not zero-click), and the memory corruption step introduces exploit reliability variance across architectures and binary builds. The 9.6 reflects these minor frictions while keeping the verdict at the CRITICAL floor dictated by the supply-chain blast radius.
Why not lower?
Dropping below CRITICAL would require evidence that (a) the vast majority of self-managed GitLab instances have closed registration AND are not network-reachable, or (b) the memory corruption is impractical to weaponize. Neither is supported by the evidence. Censys shows 86,000+ exposed instances, many with open or loosely-gated registration. The sibling CVE-2026-97229 proves the Onigmo integer-overflow class is weaponizable with a public PoC. GitLab is canonically a CI/CD and supply-chain component — by the deployment-role blast-radius rule, a viable RCE in this class of software cannot drop below CRITICAL regardless of friction, because the worst plausible outcome (supply-chain compromise) is fleet-scale.
Crowdsourced verification payload.
Run on the GitLab host itself (no arguments) or from any workstation with curl pointed at the instance URL. Local: ./check_cve_2026_110264.sh (needs read access to /opt/gitlab/). Remote: ./check_cve_2026_110264.sh https://gitlab.corp (the /api/v4/version endpoint may require a PRIVATE-TOKEN header — export it first if needed). No root required.
#!/usr/bin/env bash
# CVE-2026-110264 — GitLab CE/EE Onigmo integer overflow (unauth RCE)
# Exit: 0=PATCHED 1=VULNERABLE 2=UNKNOWN
set -euo pipefail
version_gte() {
[ "$(printf '%s\n%s' "$1" "$2" | sort -V | head -n1)" = "$2" ]
}
TARGET="${1:-}"
VERSION=""
if [ -n "$TARGET" ]; then
URL="${TARGET%/}/api/v4/version"
RAW=$(curl -skL --max-time 10 \
${PRIVATE_TOKEN:+-H "PRIVATE-TOKEN: $PRIVATE_TOKEN"} \
"$URL" 2>/dev/null || true)
VERSION=$(echo "$RAW" | grep -oP '"version"\s*:\s*"\K[^"]+' 2>/dev/null || true)
if [ -z "$VERSION" ]; then
echo "UNKNOWN — could not retrieve version from $URL"
echo " Hint: export PRIVATE_TOKEN=glpat-... and retry"
exit 2
fi
else
for F in /opt/gitlab/embedded/service/gitlab-rails/VERSION \
/opt/gitlab/version-manifest.txt; do
if [ -f "$F" ]; then
if [[ "$F" == *version-manifest* ]]; then
VERSION=$(grep -E '^gitlab-(ce|ee) ' "$F" | awk '{print $2}' | head -1)
else
VERSION=$(cat "$F" | tr -d '[:space:]')
fi
[ -n "$VERSION" ] && break
fi
done
if [ -z "$VERSION" ]; then
echo "UNKNOWN — no local GitLab found; pass URL as \$1"
exit 2
fi
fi
echo "[*] GitLab version: ${VERSION}"
MAJOR=$(echo "$VERSION" | cut -d. -f1)
MINOR=$(echo "$VERSION" | cut -d. -f2)
# Post-affected branches
if [ "$MAJOR" -gt 19 ] || { [ "$MAJOR" -eq 19 ] && [ "$MINOR" -gt 5 ]; }; then
echo "PATCHED — beyond all affected branches"
exit 0
fi
# Pre-affected
if [ "$MAJOR" -lt 7 ]; then
echo "PATCHED — predates affected range (< 7.0)"
exit 0
fi
# Map branch → fixed version
case "${MAJOR}.${MINOR}" in
18.11) FIXED="18.11.13" ;;
19.0) FIXED="19.0.10" ;;
19.1) FIXED="19.1.9" ;;
19.2) FIXED="19.2.9" ;;
19.3) FIXED="19.3.6" ;;
19.4) FIXED="19.4.3" ;;
19.5) FIXED="19.5.1" ;;
*)
# 7.0–18.10.x: no backport exists
echo "VULNERABLE — ${VERSION} (no backport; upgrade to >=18.11.13)"
exit 1
;;
esac
if version_gte "$VERSION" "$FIXED"; then
echo "PATCHED — ${VERSION} >= ${FIXED}"
exit 0
else
echo "VULNERABLE — ${VERSION} < ${FIXED} (upgrade to ${FIXED})"
exit 1
fi