Critical flaws CVE-2026-87902 + xss2shellTest my site
GitLabCVERCECI/CDDevSecOps

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.

by Thibaud Robin10 min read
GitLab: two critical flaws in the CI/CD regex engine

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

ItemCVE-2026-89078CVE-2026-93577
ClassDouble free (CWE-415), at regex parse timeInteger overflow (CWE-190), at regex compile time
CVSS v3.19.9, AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L9.9, AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
VectorCrafted regex in a CI/CD configurationCrafted regex in a CI/CD configuration
AuthenticationRequired, low privilege (PR:L)Required, low privilege (PR:L)
OutcomeMemory corruption, code execution possibleMemory corruption, code execution possible
Affected versions19.2 before 19.2.7, 19.3 before 19.3.3, 19.4 before 19.4.119.2 before 19.2.7, 19.3 before 19.3.3, 19.4 before 19.4.1
Fix19.2.7 / 19.3.3 / 19.4.1 (23 September 2026)19.2.7 / 19.3.3 / 19.4.1 (23 September 2026)
ScopeSelf-managed (GitLab.com already patched)Self-managed (GitLab.com already patched)
Reporterjoaxcar, via GitLab's HackerOne bug bountyjoaxcar, via GitLab's HackerOne bug bounty
StatusNo known exploitation, no public PoCNo 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.

Kill chain

From a low-privilege account to code execution

Phase 1Memory corruption
    Phase 2Code execution (conditional)
      Memory corruption is the documented core of the flaw. Code execution stays conditional: it assumes a crash can be turned into a reliable hijack of the execution flow.

      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 versionStatusFix
      19.2.0 to 19.2.6Vulnerable19.2.7
      19.3.0 to 19.3.2Vulnerable19.3.3
      19.4.0Vulnerable19.4.1
      19.2.7 / 19.3.3 / 19.4.1 and laterFixedAlready 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

      1. 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.
      2. 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.
      3. 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.

      Check your exposure on flawfence.com

      Let’s discuss your external exposure

      Request a personalized Flawfence demo and discover your real exposure level.