How to Check and Patch GitLab CVE-2026-110264 (CVSS 10 Unauthenticated RCE) on Self-Managed Instances

If you run a self-managed GitLab server, upgrade it today to 19.5.1, 19.4.3, 19.3.6, 19.2.9, 19.1.9, 19.0.10 or 18.11.13, whichever matches your release line. On October 21, 2026 GitLab shipped a critical patch release whose headline fix, CVE-2026-110264, is rated CVSS 10.0: an integer overflow in the regular expression compiler that “could have allowed an unauthenticated user to execute arbitrary code on the GitLab server.” It affects every GitLab CE/EE version back to 7.0. GitLab.com is already patched, and so are GitLab Dedicated tenants on 19.3.5-internal0 / 19.4.2-internal0 or later. Everyone else has work to do. This guide covers the practical part: finding your exact version, choosing the right target, upgrading the three common install types, and checking your logs for signs that someone tried it before you patched.

Everything below comes from GitLab’s critical patch release post, the two detection rules GitLab Security published in its open TLDR detections project (Org-mode render error burst and Onigmo native worker crash), GitLab’s upgrade documentation, and the CERT-EU advisory. We did not run any exploit, and you shouldn’t either.

What CVE-2026-110264 is, in one minute

GitLab’s patch post keeps the description short: an integer overflow “when compiling a specially crafted regular expression” that lets an unauthenticated user run code on the server. The detection rules GitLab Security published the same day fill in the attack path:

  • GitLab renders Org-mode files (.org, a plain-text document format it displays much like Markdown) with the org-ruby gem (version 0.9.12).
  • When it parses a #+TODO: (or #+SEQ_TODO: / #+TYP_TODO:) line, the gem interpolates the raw keyword from the file into a new Ruby regular expression.
  • A keyword with more than 32,767 repeats pushes a repeat ID in Ruby’s Onigmo regex engine past the signed 16-bit limit. The ID wraps negative and the engine performs an out-of-bounds write when the expression runs.
  • With heap grooming, GitLab says, this becomes a system() call as the git user.

The part that drives the CVSS 10 rating: according to GitLab’s rule, the attack needs no authentication, only one public project that can hold the .org payload. The rule also notes that a successful attempt can be quiet and does not reliably crash anything, so “no errors in the logs” is not proof that nothing happened.

What an attacker gets, per GitLab’s own impact list: code execution as git, the instance secrets in gitlab-secrets.json, all CI/CD variables, runner tokens and access tokens, and read/write access to every repository on the server.

The same release fixes 11 other issues: two rated High (an OmniAuth-to-LDAP auto-linking account takeover, CVE-2026-15723, CVSS 8.2, and OAuth/OIDC discovery cache poisoning, CVE-2026-112212, CVSS 7.5), five Medium and four Low. There is a nuance below about which of those you get on older release lines.

Step 1: Find your exact GitLab version

You need the full version, not just the major number, because each release line has its own fixed patch. Use whichever of these fits your setup.

From the web UI. GitLab’s Find your GitLab version page says: in the left sidebar, at the bottom, select Help > Help. The version is shown at the top of the page. You must be signed in on self-managed instances.

From the API. The Metadata API returns the version as JSON:

curl --request GET \
  --header "PRIVATE-TOKEN: <your_access_token>" \
  --url "https://gitlab.example.com/api/v4/version"

The response includes "version" (for example "19.4.2-ee") and "enterprise": true or false, which also tells you whether you need the gitlab-ee or gitlab-ce package. This is the easiest way to check many instances from a script.

From the server (Linux package). On a node running GitLab Rails:

sudo gitlab-rake gitlab:env:info

Look for the Version: line under “GitLab information”. On a self-compiled install the equivalent is bundle exec rake gitlab:env:info RAILS_ENV=production. GitLab’s docs warn that on Gitaly or other non-Rails nodes this task fails with a PostgreSQL socket error; run it on a Rails node instead.

From Docker. The image tag you started the container with usually tells you the version (gitlab/gitlab-ee:19.4.2-ee.0, for example). Check it with sudo docker ps or docker compose ps, and if the tag is latest, run the rake task above inside the container with sudo docker exec -it gitlab gitlab-rake gitlab:env:info (replace gitlab with your container name).

Step 2: Pick the fixed version for your release line

GitLab lists these as the fixed releases for CVE-2026-110264. All were published October 21, 2026, for both CE and EE:

If you runUpgrade to at leastHelm chart version
19.5.x19.5.110.5.1
19.4.x19.4.310.4.3
19.3.x19.3.610.3.7
19.2.x19.2.910.2.9
19.1.x19.1.9see version mappings
19.0.x19.0.10see version mappings
18.11.x18.11.13see version mappings
18.10 or oldera supported line above

The chart versions come from the GitLab chart’s version mappings table. Note that 19.3.6 maps to chart 10.3.7, not 10.3.6. Check that table for the older lines before you run Helm.

Three details matter when choosing:

  1. Backports carry only the critical fix. GitLab says it “backported this fix to GitLab CE/EE versions 18.11.13, 19.0.10, 19.1.9, and 19.2.9” in addition to the standard patch releases. The other 11 CVEs list affected ranges that end at 19.3.6, 19.4.3 and 19.5.1, so if you are on 19.2 or older you close the RCE but not, for example, the High-severity LDAP account-takeover bug. Treat the backport as an emergency stopgap and plan a move to a current line.
  2. 18.10 and older have no patched release. The affected range starts at 7.0, and the oldest fixed release is 18.11.13. If you are on 18.10 or earlier, you need a real upgrade, possibly with required stops. Plan it with GitLab’s Upgrade Path tool instead of jumping straight to the newest package.
  3. Expect downtime on 19.4.3 and 19.3.6. GitLab says those two versions include regular database migrations, and 19.4.3 also has post-deploy migrations. On a single-node instance, “this patch will cause downtime during the upgrade as migrations must complete before GitLab can start.” Multi-node instances can apply it without downtime by following the zero-downtime procedure.

Step 3: Back up and prepare

GitLab’s Linux package takes an automatic database backup before installing a newer version unless /etc/gitlab/skip-auto-backup exists. Still, its docs say “you should maintain a full up-to-date backup on your own.” The minimum before an emergency upgrade:

sudo gitlab-backup create

Also copy /etc/gitlab/gitlab-secrets.json (and /etc/gitlab/gitlab.rb) somewhere safe. GitLab’s backup docs warn that the backup Rake task does not store your configuration files, and that if you lose the secrets file, which holds your database encryption key, GitLab “will not be able to decrypt any encrypted values in the database.”

Then follow the pre-upgrade list from GitLab’s single-node upgrade guide:

  1. Consider turning on maintenance mode.
  2. Pause running CI/CD pipelines and jobs.
  3. Upgrade GitLab Runner to the same version as your target GitLab version.

And run the health check so you know whether problems after the upgrade are new:

sudo gitlab-rake gitlab:check

Step 4: Upgrade

Linux package (Omnibus) on Ubuntu or Debian

Pin the exact version so the package manager doesn’t jump you across a required upgrade stop. For a 19.5 EE instance:

sudo apt update && sudo apt install gitlab-ee=19.5.1-ee.0

For CE, use gitlab-ce=19.5.1-ce.0. Swap in 19.4.3, 19.3.6, 19.2.9, 19.1.9, 19.0.10 or 18.11.13 for your line.

Linux package on RHEL, AlmaLinux, Oracle Linux or Amazon Linux

GitLab’s package names include the distribution suffix:

# RHEL/AlmaLinux/Oracle Linux 9, Enterprise Edition
sudo dnf install gitlab-ee-19.5.1-ee.0.el9

# RHEL 8
sudo dnf install gitlab-ee-19.5.1-ee.0.el8

# Amazon Linux 2023
sudo dnf install gitlab-ee-19.5.1-ee.0.amazon2023

# Amazon Linux 2
sudo yum install gitlab-ee-19.5.1-ee.0.amazon2

Replace gitlab-ee/-ee.0 with gitlab-ce/-ce.0 for Community Edition. SUSE uses zypper install gitlab-ee=<version>-ee.sles15 (Leap 15.5) or gitlab-ee=<version>-ee.0.sles12. The exact syntax per distribution is on GitLab’s Linux package upgrade page.

If your package server access is slow, GitLab documents a pre-download option, for example sudo apt-get install --download-only gitlab-ee=19.5.1-ee.0, so the package is local before your maintenance window.

Docker Engine

From GitLab’s Docker upgrade guide: back up first (at least the database and the secrets file), then replace the container with one running the new image.

sudo docker stop gitlab
sudo docker rm gitlab
sudo docker pull gitlab/gitlab-ee:19.5.1-ee.0
echo $GITLAB_HOME   # must point at your existing config/logs/data volumes

Recreate the container with the same options you originally used. GitLab’s example:

sudo docker run --detach \
  --hostname gitlab.example.com \
  --publish 443:443 --publish 80:80 --publish 22:22 \
  --name gitlab \
  --restart always \
  --volume $GITLAB_HOME/config:/etc/gitlab \
  --volume $GITLAB_HOME/logs:/var/log/gitlab \
  --volume $GITLAB_HOME/data:/var/opt/gitlab \
  --shm-size 256m \
  gitlab/gitlab-ee:19.5.1-ee.0

On first start, GitLab reconfigures and runs its migrations. We confirmed on Docker Hub that the gitlab/gitlab-ee:19.5.1-ee.0, gitlab/gitlab-ce:19.5.1-ce.0, gitlab/gitlab-ee:19.4.3-ee.0 and gitlab/gitlab-ce:18.11.13-ce.0 tags exist.

The biggest Docker mistake is getting GITLAB_HOME wrong. If it’s empty or points somewhere new, the new container starts with blank volumes and looks like a fresh install. Your data is still in the old directory, but don’t let users near it until you fix the mounts.

Docker Compose

Edit the image tag in docker-compose.yml (for example image: gitlab/gitlab-ee:19.5.1-ee.0), then:

docker compose pull
docker compose up -d

Helm chart (Kubernetes)

Look up the chart version for your target in the table above, refresh your chart index, and upgrade with your existing values file. For a 19.5 instance:

helm repo update
helm search repo -l gitlab/gitlab   # confirm 10.5.1 is listed
helm upgrade gitlab gitlab/gitlab \
  --version 10.5.1 \
  -f values.yaml

If you need zero downtime, follow the chart’s upgrade guide instead of the single command. Its documented flow pauses the webservice and sidekiq deployments, runs helm upgrade with --set gitlab.migrations.extraEnv.SKIP_POST_DEPLOYMENT_MIGRATIONS=true, waits for pre-migrations, resumes the rollouts, and then runs helm upgrade a second time without that flag to apply post-deploy migrations. That matters most for 19.4.3 (chart 10.4.3), which GitLab says includes post-deploy migrations.

Step 5: Verify the patch

  1. Re-run the version check from Step 1. The API should now return 19.5.1-ee (or your line’s fixed version).

  2. Run GitLab’s post-upgrade checks from its upgrade planning guide:

    sudo gitlab-rake gitlab:check
    sudo gitlab-rake gitlab:doctor:secrets

    The second command confirms encrypted database values can still be decrypted with the current secrets.

  3. In the UI, confirm users can sign in, projects and merge requests load, and clone and push work. Check that runners pick up jobs again.

  4. Unpause CI/CD and turn off maintenance mode if you enabled it.

Step 6: Check whether anyone tried it before you patched

GitLab’s patch post points self-managed customers to two threat detections. Both are written as Sigma rules, and both include plain grep versions for instances without a SIEM. The paths below are for a Linux package install. On Helm, the same logs come from the webservice container of the gitlab-webservice-* pods. On a source install, look under /home/git/gitlab/log/.

Signal 1: a burst of Org-mode render timeouts

An attack is a grind. Most attempts make the renderer exceed its time budget, and each time GitLab logs a Timeout::Error from the OtherMarkup renderer on a .org file in exceptions_json.log. GitLab’s rule fires when one client produces five or more in 30 minutes. The raw query from the rule:

EXC=/var/log/gitlab/gitlab-rails/exceptions_json.log

# Org-mode render timeouts, counted per client (a burst = rendering campaign)
grep '"exception.class":"Timeout::Error"' "$EXC"* \
  | grep '"extra.class_name":"OtherMarkup"' | grep -E '"extra.file_name":"[^"]*\.org"' \
  | grep -o '"meta.client_id":"[^"]*"' | sort | uniq -c | sort -rn

A meta.client_id of ip/... means the requests were unauthenticated. The rule also suggests checking for the editor preview being forced onto the Org renderer for a non-Org file. GitLab says any hit on that one is notable:

PROD=/var/log/gitlab/gitlab-rails/production_json.log
grep 'Projects::BlobController#preview' "$PROD"* | grep '"file_path"' | grep -E '\.org"'

And in any suspect repository checkout, look for the payload itself, an oversized #+TODO: line:

rg -l --glob '**/*.org' '^\#\+(TODO|SEQ_TODO|TYP_TODO):.{500,}'

Signal 2: native Ruby crashes through org-ruby or Onigmo

When the overflow faults instead of running cleanly, Ruby writes a [BUG] crash report to the Puma worker’s stderr. GitLab’s second rule looks for those reports with org-ruby or Onigmo frames:

PUMA=/var/log/gitlab/puma/puma_stderr.log
grep -B1 -A20 '\[BUG\]' "$PUMA"* \
  | grep -E 'org-ruby|headline\.rb|regexp_helper\.rb|regexp_matcher\.rb|match_at|onig_search_gpos|onig_compile|compile_tree|compile_length_tree|rb_str_sub_bang'

GitLab is explicit that this signal only corroborates. A successful exploit “is quiet and does not reliably crash,” so finding nothing here “never clears an attempt or a success.” The rule also notes that many instances don’t ship puma_stderr.log to their log pipeline by default, so confirm you actually collect it.

The same rule covers two earlier Onigmo-related CVEs reached through CI regex rules, CVE-2026-93577 and its bypass CVE-2026-97229. GitLab lists their fixed versions as 19.4.2, 19.3.5 and 19.2.8. Upgrading to this week’s releases covers those too.

If you find something

GitLab’s response guidance for a hit, condensed:

  • Treat a burst as a probable exploitation attempt. A quiet period after a campaign “can indicate success, not a stop.”
  • Identify the source from meta.remote_ip, pull the .org files named in extra.file_name, and find who committed them.
  • Block the source IP, and remove or make private the project holding the payload.
  • Hunt for code execution: unexpected child processes of Puma, new files owned by git in /tmp, outbound connections, and changed crontabs or SSH keys for the git user.
  • If you see any sign of execution, treat the instance as compromised and rotate the secrets in gitlab-secrets.json, all CI/CD variables, all runner tokens and all access tokens. Keep exceptions_json.log, production_json.log, application_json.log, the Workhorse logs and the payload files as evidence.

That rotation list is the same one we covered for the Railway environment variable incident: if a build system was exposed, assume every secret it could read is burned.

If you can’t upgrade today

GitLab’s patch post offers no workaround. The only interim advice is in the detection rule: “until patched, reduce public visibility where possible,” because the unauthenticated path needs a public .org file that GitLab renders. Concretely, that means making public projects internal or private, or blocking anonymous access at your reverse proxy, while the detection queries above run. This narrows the unauthenticated route. It does not fix the bug. Anyone who can sign in and push an .org file may still reach the vulnerable code.

CERT-EU’s advice is the same in different words: update as soon as possible, prioritize internet-facing instances, and run a compromise assessment.

Troubleshooting

apt says the version isn’t found. Run sudo apt update first and check that you’re using the right package name (gitlab-ee vs gitlab-ce). The API’s enterprise field tells you which edition you have. The version string needs the -ee.0 or -ce.0 suffix.

gitlab:env:info fails with PG::ConnectionBad. You ran it on a Gitaly or other non-Rails node. GitLab’s docs say to run it only on nodes running GitLab Rails.

The instance is down longer than usual after upgrading to 19.4.3 or 19.3.6. Those releases include database migrations, and a single-node instance stays down until they finish. That’s expected, so give the migrations time to complete before assuming the upgrade failed.

Docker container comes up as a fresh install. GITLAB_HOME was empty or wrong when you recreated the container. Stop it, fix the variable, and recreate the container with your original volume paths.

Helm upgrade fails on chart version. Run helm repo update and then helm search repo -l gitlab/gitlab to confirm the chart version exists in your local index. Remember that GitLab 19.3.6 is chart 10.3.7.

FAQ

Is GitLab.com affected? No action needed. GitLab says GitLab.com “is already running the patched version.”

What about GitLab Dedicated? Tenants on 19.3.5-internal0 and 19.4.2-internal0 or later already include the fixes.

Is it being exploited? GitLab’s post doesn’t report exploitation, and as of this writing we found no official statement that it is. GitLab did publish detections “to support customers with self-managed instances,” and its rule now describes the attack path in detail, so don’t wait for exploitation reports. Patch first and check the logs afterward.

Does disabling Org-mode rendering help? GitLab hasn’t documented that as a mitigation, so we won’t suggest it. The official position is that upgrading is the fix.

Who found it? GitLab credits researcher joaxcar, who reported it through GitLab’s HackerOne bug bounty program.

Do I need to upgrade GitLab Runner too? GitLab’s upgrade guides say to upgrade Runner to the same version as your target GitLab version as part of the normal procedure. The CVE itself is in the GitLab server.

The takeaway

This is the second pre-authentication RCE this week in widely self-hosted software. The first was PHP2Shell in PHP itself. The response pattern is the same: find every instance, including the forgotten one under someone’s desk, patch to the fixed version for its line, run the vendor’s detections over the logs from before you patched, and rotate secrets if anything looks off. If you want a second pair of eyes on your own code while you’re at it, our guide to Codex Security shows how to set up automated scanning. More hands-on guides are on the 7minai homepage.