GitLab CVE-2026-110264 (CVSS 10.0) Lets Unauthenticated Attackers Run Code by Rendering an .org File
Contents
TL;DR
What happened October 21, 2026: GitLab fixed CVE-2026-110264 (CVSS 10.0). Rendering a crafted .org file lets an attacker with no login run commands on the GitLab server. Every self-managed GitLab CE/EE version from 7.0 on is affected
What to do
- Upgrade to 19.5.1, 19.4.3, 19.3.6, 19.2.9, 19.1.9, 19.0.10, or 18.11.13
- On 18.10 or older, follow the upgrade path to 18.11.13 or later
- If you already installed last week’s fixes (19.4.2 and others), upgrade again
GitLab.com and Dedicated GitLab.com is already patched. Dedicated instances on 19.3.5-internal0 or 19.4.2-internal0 and later need no action
On October 21, 2026, GitLab shipped a critical patch release, an out-of-band fix outside the regular release schedule: 19.5.1, 19.4.3, 19.3.6, 19.2.9, 19.1.9, 19.0.10, and 18.11.13 (GitLab Critical Patch Release: 19.5.1, 19.4.3, 19.3.6, 19.2.9, 19.1.9, 19.0.10, 18.11.13). It contains 12 security fixes. On GitLab’s four-level severity scale (Critical, High, Medium, Low), one is Critical (CVE-2026-110264) and two are High.
CVE-2026-110264 is an integer overflow during regular expression compilation. Under the right conditions, an unauthenticated user can run arbitrary code on the GitLab server.
The CVSS score is 10.0 (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H): exploitable over the network, with no login and no user interaction, and the impact reaches beyond GitLab itself.
Every version from 7.0 on is affected. On top of the usually supported releases (19.3 to 19.5), GitLab backported the fix to 19.2, 19.1, 19.0, and 18.11.
GitLab’s third regex bug in a month
Since September, GitLab has fixed a run of vulnerabilities that feed attacker input into Onigmo, Ruby’s regular expression engine. All three came through GitLab’s HackerOne bug bounty program, and all three were reported by joaxcar.
| Published | CVE | CVSS | Entry point | Fixed in |
|---|---|---|---|---|
| September 23, 2026 | CVE-2026-93577 | 9.9 (login required) | Regex in CI/CD config | 19.4.1, 19.3.3, 19.2.7 |
| October 12, 2026 | CVE-2026-97229 | 10.0 (no login) | Regex in CI/CD config | 19.4.2, 19.3.5, 19.2.8 |
| October 21, 2026 | CVE-2026-110264 | 10.0 (no login) | Rendering a .org file | 19.5.1, 19.4.3, 19.3.6, 19.2.9, 19.1.9, 19.0.10, 18.11.13 |
Sources are the release posts (19.4.1, 19.4.2, 19.5.1).
GitLab’s security team published detection rules, and their write-up calls CVE-2026-97229 a bypass of CVE-2026-93577: a different attack that slips past the earlier fix (Onigmo native worker crash).
In the two CI/CD bugs, a pattern written in regexp under rules: compiled to more than 2 GiB, and a signed 32-bit jump target wrapped around to a negative value.
Those two only affected 19.2 and later.
CVE-2026-110264 enters through the code that renders a .org file in a repository, not through CI/CD, and it reaches back to 7.0.
The October 12 fixes (19.4.2 and the rest) are still vulnerable, so you have to upgrade again even if you installed them.
From an .org file to code execution
.org is the text format used by Emacs Org-mode. GitLab renders .org files in a repository to HTML with a Ruby gem called org-ruby.
Org-mode lets a headline start with a state keyword such as TODO or DONE, and a #+TODO: line in the file defines which keywords exist.
According to GitLab’s detection write-up (Org mode render error burst), org-ruby 0.9.12 escapes the keywords when it builds a regex from the keyword list.
A second step, which strips the keyword from the headline, passes the keyword to Regexp.new without escaping it.
In the gem, line 99 of lib/org-ruby/headline.rb reads like this.
@headline_text.sub!(Regexp.new("^#{@keyword}\s*"), "")
Whatever you write on the #+TODO: line becomes a regex pattern as-is, so anyone who can put a .org file in a repository decides which regex GitLab compiles.
Onigmo numbers each counted repeat such as {0,100} (a repeat ID) and stores that number as a signed 16-bit value in the compiled program.
Past 32,767 repeats, the number wraps negative.
At match time Onigmo uses that number to pick a stack position, so it writes outside the allocated area (an out-of-bounds write).
The write-up says that by shaping memory layout through the request, an attacker can turn this into a call to system() and run commands as GitLab’s git user.
It also says no login is needed: one public project holding the payload (the malicious .org file) is enough.
graph TD
A["Render an .org file"] --> B["org-ruby reads the<br/>#+TODO: keywords"]
B --> C["Keyword goes to Regexp.new<br/>without escaping"]
C --> D["Onigmo stores repeat IDs<br/>as 16-bit values"]
D --> E["Past 32,767 repeats<br/>the ID goes negative"]
E --> F["Out-of-bounds write<br/>during matching"]
F --> G["Commands run as<br/>the git user"]
The preview in the web editor is a second way in, alongside the page that displays a .org file.
The preview picks a renderer from the file_path it receives, so even while editing a .md file, sending a file_path ending in .org runs the Org-mode renderer.
In the detection rule’s sample, this path is logged as a signed-in user (user/42).
The patch to GitLab’s bundled Ruby
The bug fix list for 19.5.1 includes Add Ruby regexp repeat ID overflow patch for 3.3, 3.4 and 4.0.
The same entry is in the release notes for 19.4.3, 19.3.6, and 19.2.9. GitLab fixed the bug by patching the Ruby bundled with Omnibus (the official GitLab package).
The omnibus-gitlab commit (Add Ruby regexp repeat ID overflow patch for 3.3, 3.4 and 4.0) says the patch is a straight backport of Ruby PR #16801, “Reject overflowed repeat/null-check IDs in huge regexps”.
When a repeat ID or a null-check ID (the number used to stop infinite loops in repeats that can match the empty string) exceeds the limit, compilation fails with RegexpError.
The PR was merged into Ruby master on April 29, 2026, but the commit message says the fix exists only on master (Ruby 4.1) and not on the ruby_3_3, ruby_3_4, or ruby_4_0 branches.
On a fixed Ruby, the patterns from PR #16801’s regression tests fail at compile time with RegexpError: too many range repeat and too many null check.
On my M4 Mac mini, I passed the same patterns to Regexp.new on Ruby 4.0.6 from Homebrew.
This is not the Ruby bundled with GitLab, and the test only checks whether compilation succeeds.
{ "range repeat" => '(?:foobar){0,100}' * 100000,
"null check" => '(?:(?:foo)?|(?:bar)?)*' * 100000 }.each do |name, src|
begin
Regexp.new(src)
puts "#{name}: compiled (no error)"
rescue RegexpError => e
puts "#{name}: RegexpError: #{e.message}"
end
end
ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [arm64-darwin25]
range repeat: compiled (no error)
null check: compiled (no error)
Both patterns compiled without an error.
Since the fix exists only on Ruby master, any Ruby application that builds a pattern with Regexp.new from outside input may be affected too, GitLab or not. I only tested 4.0.6.
If you installed GitLab from source and run it on the system Ruby, upgrading GitLab does not apply this patch to the system Ruby.
The release post has no instructions for source installs.
Your GitLab version and what to upgrade to
For Omnibus and Docker installs, either of these shows the version.
You can also sign in as an administrator and open /help in a browser.
# Omnibus
sudo gitlab-rake gitlab:env:info | grep -A1 '^GitLab information'
cat /opt/gitlab/embedded/service/gitlab-rails/VERSION
# Docker (container named gitlab)
docker exec gitlab cat /opt/gitlab/embedded/service/gitlab-rails/VERSION
| Current version | Upgrade to |
|---|---|
| 19.5.0 | 19.5.1 |
| 19.4.0 to 19.4.2 | 19.4.3 |
| 19.3.0 to 19.3.5 | 19.3.6 |
| 19.2.0 to 19.2.8 | 19.2.9 |
| 19.1.0 to 19.1.8 | 19.1.9 |
| 19.0.0 to 19.0.9 | 19.0.10 |
| 18.11.0 to 18.11.12 | 18.11.13 |
| 18.10 and older (from 7.0) | No fixed release. Follow the upgrade path to 18.11.13 or later |
There is no fixed release for 18.10 and older, so you have to move to a newer major or minor version. GitLab has required upgrade stops you cannot skip: 18.2, 18.5, 18.8, and 18.11 in the 18 series, and 19.2 and 19.5 in the 19 series (Upgrade paths). At each stop you wait for background migrations (jobs that bring the database schema and data up to the new version) to finish before going further, so coming from an old version takes a while. The Upgrade Path tool lists the versions to go through when you enter your current and target versions.
19.4.3 and 19.3.6 include database migrations that run during the upgrade. A single-node GitLab does not start until they finish, so it is down during the upgrade. Multi-node installs can follow the zero-downtime procedure and stay up.
What to grep for in your logs
With this release, GitLab published two detection rules for self-managed instances in Sigma, a common format for log detection rules.
You do not need a SIEM (a system that collects and analyzes logs). On Omnibus you can grep the logs for the same conditions.
| Detection rule | Log to check (Omnibus) | Condition |
|---|---|---|
| Org mode render error burst | /var/log/gitlab/gitlab-rails/exceptions_json.log | Timeout::Error while rendering a .org file, 5 or more times from the same client within 30 minutes |
| Onigmo native worker crash | /var/log/gitlab/puma/puma_stderr.log | A Ruby [BUG] crash report that mentions org-ruby or Onigmo functions such as match_at or onig_compile |
Most attempts fail. Each one either times out the renderer or takes down the whole worker (the process that handles requests).
Timeouts land in exceptions_json.log.
The detection rule narrows them to timeouts on .org files in OtherMarkup, the code that renders formats other than Markdown.
sudo grep '"exception.class":"Timeout::Error"' /var/log/gitlab/gitlab-rails/exceptions_json.log \
| grep '"extra.class_name":"OtherMarkup"' \
| grep '\.org"'
If anything matches, look at meta.client_id (ip/<IP address> when not signed in) and meta.project to see who rendered which project’s .org file.
When a worker crashes, Ruby writes a crash report that starts with [BUG].
sudo grep -n -A30 '\[BUG\]' /var/log/gitlab/puma/puma_stderr.log \
| grep -E 'org-ruby|headline\.rb|match_at|onig_search_gpos|onig_compile|compile_tree'
For the two CI/CD bugs fixed in September and on October 12, an attack through a pipeline crashes a Sidekiq worker (the process that runs background jobs).
That crash shows up in /var/log/gitlab/sidekiq/current.
The write-up notes that a successful attack may leave nothing in the logs and does not always crash anything.
An empty log does not mean you were not attacked.
It also says puma_stderr.log is often not shipped to a SIEM by default, so depending on your log collection, the crash reports may never reach your SIEM.
The other 11 security fixes in this release
Two of the other 11 fixes are rated High.
| CVE | What it is | CVSS | Affected |
|---|---|---|---|
| CVE-2026-15723 | With OmniAuth SAML and LDAP auto-linking both enabled, a signed-in user can take over an existing LDAP account | 8.2 | 10.1 and later |
| CVE-2026-112212 | Poisoning a shared cache with the OAuth/OIDC discovery response (the URL that returns the auth server’s configuration) can redirect users to an external server | 7.5 | 18.3 and later |
These two and the remaining nine Medium and Low fixes are in 19.3.6, 19.4.3, and 19.5.1. Going by the release post, the backports in 19.2.9 and older cover only CVE-2026-110264 and do not include the other 11 fixes.
Workarounds and exploitation status
The release post gives no workaround for instances that cannot upgrade. I could not find a setting in GitLab’s documentation that disables Org-mode rendering alone.
The release post does not say whether the bug has been exploited in the wild.
It is also not in CISA’s Known Exploited Vulnerabilities (KEV) catalog as of the version updated on October 21, 2026 (US time).
The detection write-up is based on logs from reproducing the attack in GitLab’s test environment, and one of them was a crash (Bus Error) on arm64-darwin, the same arm64 macOS I ran my test on.