GitLab: two critical flaws in the CI/CD regex engine
Two critical flaws (CVSS 9.9) in GitLab CE/EE's regular expression engine: a crafted CI/CD regex corrupts server memory and can lead to code execution. Affected versions, version-based detection, the 19.2.7 / 19.3.3 / 19.4.1 fix.

Two critical flaws in the same patch
On 23 September 2026, GitLab shipped a critical patch for the self-managed Community and Enterprise editions, in versions 19.4.1, 19.3.3 and 19.2.7. Eleven vulnerabilities are addressed. Two of them share the same 9.9 score and the same entry point: the regular expression engine GitLab uses to evaluate the regexes in your CI/CD files.
CVE-2026-89078 is a double free, CVE-2026-93577 an integer overflow. In both cases a crafted regex, placed in a CI/CD configuration, corrupts server memory while it is being processed.
One detail governs everything else: both flaws require an account. That sets them apart from CVE-2026-85706, the unauthenticated file read fixed two weeks earlier. The privilege needed stays low, but it is not zero.
The facts, as the advisory states them
| Item | CVE-2026-89078 | CVE-2026-93577 |
|---|---|---|
| Class | Double free (CWE-415), at regex parse time | Integer overflow (CWE-190), at regex compile time |
| CVSS v3.1 | 9.9, AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L | 9.9, AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H |
| Vector | Crafted regex in a CI/CD configuration | Crafted regex in a CI/CD configuration |
| Authentication | Required, low privilege (PR:L) | Required, low privilege (PR:L) |
| Outcome | Memory corruption, code execution possible | Memory corruption, code execution possible |
| Affected versions | 19.2 before 19.2.7, 19.3 before 19.3.3, 19.4 before 19.4.1 | 19.2 before 19.2.7, 19.3 before 19.3.3, 19.4 before 19.4.1 |
| Fix | 19.2.7 / 19.3.3 / 19.4.1 (23 September 2026) | 19.2.7 / 19.3.3 / 19.4.1 (23 September 2026) |
| Scope | Self-managed (GitLab.com already patched) | Self-managed (GitLab.com already patched) |
| Reporter | joaxcar, via GitLab's HackerOne bug bounty | joaxcar, via GitLab's HackerOne bug bounty |
| Status | No known exploitation, no public PoC | No known exploitation, no public PoC |
A 9.9 for authenticated flaws can be surprising. It comes from the S:C in the vector: the corruption does not stay confined to the application, it reaches the server process. The last letter separates the two CVEs, A:H against A:L. The integer overflow brings the service down reliably, the double free weighs less on availability. So the certain consequence of these bugs is a crash. Code execution is the upper bound: the advisory keeps it, but it is not a given on every configuration.
The mechanism
GitLab evaluates regexes that you write, in several places along the CI/CD chain: job trigger rules (rules:if), branch or tag filters, workflow conditions. That regex is a string supplied by whoever authored the pipeline. To evaluate it, the server parses it first, then compiles it. Each CVE targets one of those two steps.
The double free (CVE-2026-89078) happens at parse time. On a certain shape of pattern, the parser frees the same block of memory twice. The allocator ends up in an inconsistent state, and a later allocation can land on a block already returned. The integer overflow (CVE-2026-93577) comes later, at compile time: computing the size of a structure overflows the range of an integer, the engine reserves too little memory, and the write that follows spills out of the buffer.
The root cause is shared. The text of the regex drives memory operations whose edge cases are not all bounded. This is not an injection you spot at a glance: it takes a precise shape of pattern, tuned to the implementation, to reach the defect. That is also why no exploit is circulating yet. Finding the pattern that crashes does not yield reliable code execution, and the gap between the two is measured in weeks of development.
From a low-privilege account to code execution
Who can submit a CI/CD regex on your instance?
The real reach of these flaws comes down to that question. On an instance that allows open sign-up, or that accepts pipelines proposed by outside contributors, a recent account with a modest role is enough to push a CI/CD configuration. This is the most exposed case, and why GitLab.com was patched without delay.
On an internal instance restricted to employees, an attacker first needs access. The risk does not vanish for all that. An intern's account, a CI token left in a repository, a compromised developer workstation, and the attacker moves from read access to code on the server that holds your secrets and deployment keys. This is the lateral move that turns a minor incident into a compromise of the whole build chain.
Affected versions
The advisory marks out three branches: 19.2 before 19.2.7, 19.3 before 19.3.3, 19.4 before 19.4.1. The fix landed on 23 September 2026.
| GitLab version | Status | Fix |
|---|---|---|
| 19.2.0 to 19.2.6 | Vulnerable | 19.2.7 |
| 19.3.0 to 19.3.2 | Vulnerable | 19.3.3 |
| 19.4.0 | Vulnerable | 19.4.1 |
| 19.2.7 / 19.3.3 / 19.4.1 and later | Fixed | Already applied |
Checking your exposure
There is no safe remote test here. Proving exposure to a memory corruption would mean sending the regex to a real pipeline, that is, trying to bring the target server down. No benign marker gets around that, unlike a path traversal you confirm by making an app include a public file. On a production asset, testing means exploiting. The version stays the only reliable judge.
Three ways to read it, from the safest to the most convenient:
- On the server,
sudo gitlab-rake gitlab:env:info(Omnibus install) gives the exact version. - Through the API,
curl --header "PRIVATE-TOKEN: <token>" https://gitlab.example.com/api/v4/version, which requires a token. - From the UI, the help page (
/help) once logged in.
All three assume access to the instance. GitLab does not publish its patch number to an anonymous visitor, on purpose, which remains a limit for any inventory run from the outside.
Remediation
- Upgrade to 19.2.7, 19.3.3 or 19.4.1 depending on your branch. This is the only remediation that actually closes both flaws, and it imposes no major version jump.
- Inventory your self-managed GitLab instances, including the ones nobody claims: a project's test instance, a subsidiary's GitLab, the one on a forgotten subdomain. That is usually the one running a version behind.
- While the upgrade is pending, tighten access: turn off open sign-up if you do not need it, review the roles allowed to edit a CI/CD file, watch for project creation by recent accounts.
GitLab, a recurring target
In one month GitLab has seen an unauthenticated file read at 10.0, then these two memory corruptions at 9.9. The vector changes, the conclusion holds: a self-managed instance concentrates the code, the secrets and the deployment keys of an organization, which makes it a target of choice.
An annual pentest catches none of these flaws: a disclosure dated 23 September is absent from a report written in the spring. What catches them is an up-to-date inventory of exposed assets and monitoring that spots the instance left one patch behind, without waiting for the next audit. For that you first have to know your whole perimeter, including the GitLab nobody declared.
Where Flawfence fits in
Since both flaws are authenticated, no passive external scanner proves their exploitability from the outside, and ours does not claim to. Its value is elsewhere, on what is most often missing: finding the GitLab instance you had forgotten.
Faced with this kind of advisory, Flawfence discovers your exposed GitLab instances across all your domains and subdomains, shadow IT included, reports the version when it is readable, flags every self-managed instance as a sensitive asset to keep up to date, and monitors continuously so a newly exposed GitLab or a version rollback triggers an alert the same day.
Frequently asked questions
Do these flaws require an account?
Yes. The vector for both CVEs carries PR:L: a low-privilege account is needed, able to submit a CI/CD configuration containing the crafted regex. It is not exploitable by an anonymous attacker, unlike the CVE-2026-85706 file read from the same month. On an instance that allows open sign-up, the bar stays low.
Are they being actively exploited?
At patch release, no exploitation in the wild was reported and no public exploit code was circulating. Turning a memory corruption into reliable code execution takes exploit work that is not immediate. That is no reason to wait: the patch describes the defect, and marks out the path for anyone who sets out to write that exploit.
Memory corruption and RCE, are they the same?
No, and the advisory stays careful by speaking of "possible" code execution. The certain consequence is instability, a crash of the GitLab process, so a denial of service. Arbitrary code execution is the upper bound, reachable depending on the configuration and system hardening. We treat these flaws as critical without overstating the certainty of an instant RCE.
Can a WAF protect me in the meantime?
Not reliably. The regex travels inside a legitimate CI/CD configuration, not in a suspicious parameter. Telling a dangerous pattern from an ordinary one without blocking valid pipelines is beyond a generic rule. Upgrading to 19.2.7, 19.3.3 or 19.4.1 is the only real remediation.
How do I check my version without exposing the instance?
On the server, sudo gitlab-rake gitlab:env:info gives the exact version. In the UI, the help page (/help) shows it once logged in. Through the API, GET /api/v4/version returns it, but requires a token. There is no reliable way to read the patch number from the outside without authentication, and that is by design.
What to take away
A user-supplied regular expression is an input like any other: the code that parses or compiles it has to bound its edge cases, or it ends up writing where it should not. The difficulty is not testing, you cannot do it remotely without risk, but knowing where your GitLab instances are and what version they run. That is inventory work, not exploit work.
Let’s discuss your external exposure
Request a personalized Flawfence demo and discover your real exposure level.
