Blog / CVE explainers

CVE-2026-97229: the asterisk on GitLab’s ‘unauthenticated’ RCE

GitLab’s CVSS 10.0 and CISA’s KEV call this unauthenticated. The only two people who made it work needed a GitLab account first. Here’s why that matters.

Completed doctoral studies in Information SecurityUniversity defense passed · Final dissertation defense pending

Zero credentials on paper. One account in practice.

Zero. That’s how many credentials GitLab’s own CVSS vector requires for CVE-2026-97229: AV:N/AC:L/PR:N/UI:N, a perfect 10.0, arbitrary code execution with no privileges needed. One. That’s how many accounts the only two people who actually made this work, independently, needed before they could send the request that did anything. Both numbers are true. They describe two different steps in the same attack.

CVE-2026-97229 is a critical integer overflow in GitLab’s CI/CD Regular Expression Compiler, patched 12 October 2026 across the 19.2, 19.3 and 19.4 branches.[1] GitLab’s own advisory describes exactly what that score implies: a crafted regular expression in a CI/CD configuration, sent by an unauthenticated user, turning into arbitrary code execution on the server, under certain conditions.[1] CISA added it to the Known Exploited Vulnerabilities catalog less than three weeks later, confirming it’s being used against real targets.[2] None of that is wrong. It just skips the one detail that tells you how exposed you actually are: reaching the unauthenticated step requires an account first.

What GitLab actually shipped, and when

GitLab dated the fix release 12 October 2026, shipping 19.2.8, 19.3.5 and 19.4.2 for the three affected branches — 19.2 before 19.2.8, 19.3 before 19.3.5, 19.4 before 19.4.2.[1] GitLab.com was already running a patched build before the advisory went out, and GitLab Dedicated customers needed to do nothing at all.[1] The researcher who reported it used the HackerOne handle joaxcar, who separately reported a medium-severity open-redirect bug, CVE-2026-7767, fixed in the same release.[1]

One detail shifted after the fact, worth knowing before you check your version number. GitLab’s release notes initially pointed the 19.3 branch at 19.3.4; coverage the next day reported that build actually shipped an unrelated fix for a merge-request-page bug, and that 19.3.5 is the real fixed release for this CVE, which the current advisory now reflects.[8] After that, things moved quickly: Rapid7 reported an independent working reproduction roughly a day after disclosure, a named public proof-of-concept followed about four days after that, and CISA’s confirmed-exploitation listing landed roughly seventeen days after the original patch.[1][2][4][7] That’s not this piece’s main story. It’s still worth sitting with: a CVSS 10.0 in a product like GitLab gets weaponized fast once the patch itself tells attackers exactly where to look.

The regex engine that executes your command instead of matching your string

I haven’t run this myself. Everything below comes from the proof-of-concept Jonah Burgess (GitHub: jburgess-r7) published about four days after the patch, explicitly labelled ‘an initial PoC release,’ from Rapid7’s separately reported reproduction, and from GitLab’s own advisory, all read as of 2 November 2026.[1][3][4] The trigger lives behind GitLab’s CI-lint endpoint, the same one GitLab itself uses to validate a .gitlab-ci.yml file before any pipeline runs. Feed it a crafted regular expression in the CI/CD configuration, paired with a repository branch whose path uses a particular UTF-8 naming trick, and the regex compiler’s own length accounting overflows.[3]

That overflow doesn’t just crash the worker, though it often does that too. It redirects where the regex engine’s execution goes next, straight into a function that runs an arbitrary command.[3] The command runs as GitLab’s low-privilege ‘git’ service account, not as an administrator and not as root on the host.[3] That’s a real ceiling on the blast radius, worth stating plainly: this is server-side code execution, not an instant path to full admin control. From a ‘git’ shell, an attacker already has plenty worth taking, starting with repository contents and whatever CI secrets that account can read.

// The CI-lint trigger, reconstructed from Burgess's public PoC. Paraphrased
// shape of the chain -- not the working payload itself.

1. Attacker-controlled branch is pushed to a public project, with a
   specially-named (UTF-8) path and a crafted regex in .gitlab-ci.yml.
2. An unauthenticated request hits GitLab's CI-lint endpoint, which
   validates that regex before any pipeline actually runs.
3. The regex compiler's internal length accounting overflows -- an
   integer wraps where it shouldn't.
4. Execution inside the regex engine is redirected into a function that
   runs an OS command, as the low-privilege "git" service account.
5. On a vulnerable build, the worker process frequently crashes right
   after (a bare HTTP 502 is a side effect, not proof by itself).

The step that happens before the unauthenticated one

Here’s the detail every CVSS-10 headline skips. Burgess’s own README states the preconditions plainly: an ordinary account capable of pushing a branch to a public project on the target instance, a target running the exact official gitlab/gitlab-ce:19.4.1-ce.0 build (anything else needs rebuilt binary offsets), roughly 9 GiB of free memory, and network reachability back to a listener if you want the command’s output.[3] The CI-lint request that actually triggers the overflow needs no token, cookie or authorization header at all, that part really is unauthenticated.[3][4] It’s just not the first step.

Per Help Net Security’s reporting, Rapid7 independently reproduced working code execution against an unpatched official GitLab CE 19.4.1 image about a day after disclosure (the identical input failed against the patched 19.4.2 image), and described the same gate: an ‘ordinary account’ pushing the crafted branch, viable ‘on self-managed instances that allow public project creation.’[4] Two separate parties, working apart, landed on the identical precondition. I’ll flag one thing the record doesn’t actually say, because it’s tempting to merge them: the PoC’s GitHub handle carries a ‘-r7’ suffix, and its own README links out to ‘Rapid7’s technical analysis’ of this same bug. No source states that connection directly, so I’m treating Burgess’s PoC and Rapid7’s reproduction as what the record actually shows: two independently-arrived-at confirmations of the same precondition, not one.

The PoC author is upfront about its limits, too: 10 out of 10 trials succeeded against that one tested build with ASLR enabled, the identical input returns false against the patched 19.4.2 compiler, and the author states directly that ‘analysis of other affected configurations and reachable paths is ongoing’ and that the PoC ‘does not establish real-world exploitation in intrusions.’[3] A successful trigger often returns a bare HTTP 502, because the worker crashes right after running the command, so a 502 alone, without the rest of the pattern, proves nothing.[3] Hold onto that distinction. It matters for what you can actually tell from your own logs.

How far ‘unauthenticated’ actually stretches

So which is it, unauthenticated or not? Both, depending on which boundary you’re asking about. GitLab’s CVSS vector rates the vulnerable component PR:N, no privileges required, and that’s accurate for exactly what it’s scoring: the CI-lint request itself checks no credential.[1] But “the component requires no auth” and “the attack chain requires no auth” are two different claims, and the only two named parties who got this working in practice, Burgess and Rapid7, independently agree the chain needs a step before that request: an account that can push a branch to a public project.[3][4] GitLab’s severity rating isn’t wrong. It’s answering a narrower question than the headline implies.

Whether that precondition is trivial or a real wall depends on settings most admins don’t think of as mitigations for this CVE. GitLab’s own documentation says self-registration is open to any visitor by default on a self-managed instance, and recommends disabling it on anything public-facing that doesn’t expect open sign-ups.[5] It also says admin approval of new sign-ups has been the default for new instances since GitLab 13.6, so a reasonably current, reasonably configured instance already has a human in the loop before a stranger gets an account.[5] Project visibility, and who’s allowed to create a public project, is separately admin-configurable.[5] None of that is a patch. All of it changes how many strangers can even reach the unauthenticated half of this bug.

// For a stranger to turn this into RCE against your instance,
// three things have to be true at once:

1. Your instance allows self-registration, OR the attacker already
   holds any ordinary account on it.
2. That account (or self-registration) can create, or push a branch
   to, a PUBLIC project -- not just a private one.
3. The instance is still running an unpatched build
   (before 19.2.8 / 19.3.5 / 19.4.2) and is reachable from wherever
   the attacker is.

Close registration, restrict who can make projects public, or patch --
any one of the three breaks the chain. Patching is still the only fix
that doesn't depend on a setting staying the way you left it.

Confirmed exploited. Confirmed almost nothing else.

CISA added CVE-2026-97229 to its Known Exploited Vulnerabilities catalog on 29 October 2026, under the name ‘GitLab Community Edition and Enterprise Edition Integer Overflow or Wraparound Vulnerability,’ with a required-action due date of 1 November under Binding Operational Directive 26-04.[2] That date-added is not a claim about when exploitation actually started. KEV entries routinely lag real first use in the wild, and no source here states when attacks against this bug actually began. Read ‘29 October’ as when CISA confirmed it, not as a timestamp on the attacks themselves.

Here’s the honest scale of what ‘confirmed exploited’ means in this record, worth being precise about because the record is thinner than the headline suggests: no source reviewed for this piece, not CISA’s own entry, not GitLab’s advisory, not the BleepingComputer coverage of it, publishes an indicator of compromise, a named victim, or any technique-level account of what the observed attacks actually did.[1][2][6] BleepingComputer’s follow-up cites GitLab’s own threat-detection releases and Shadowserver’s count of roughly 2,800 internet-exposed GitLab CE instances, and that’s the extent of the public detail available.[6] ‘Confirmed exploited’ here rests on CISA’s listing as the named authority, not on attack evidence this piece could independently examine. That’s a legitimate basis for the determination. It’s also the ceiling of what I can honestly tell you about the attacks themselves.

The patch, the rules, and what to check this week

The fix is versioned exactly: 19.2.8, 19.3.5 and 19.4.2, all released 12 October 2026.[1] If your 19.3 branch shows 19.3.4, that build fixed an unrelated merge-request-page bug, not this CVE, 19.3.5 is the one you actually want.[8] Alongside the patch, GitLab shipped six new threat-detection rules for self-managed instances, named specifically for this issue: Onigmo native worker crash, GraphQL cilint hostile regexp, ci regexp oversized non-ascii path, ci regexp guard rejection, ci lint workhorse worker crash, and ci lint scripted client error burst.[1] If your own monitoring or GitLab’s detections surface any of those, treat it as a signal worth chasing, not noise.

GitLab’s own disclosure policy publishes full issue-tracker detail for a patched vulnerability 90 days after the fixing release, which puts anything beyond the advisory text at roughly mid-January 2027 by simple arithmetic, not a date GitLab itself states, just what their own 90-day rule works out to.[1] Until then, here’s what the public record actually supports checking.

Patch and verify, in order:
  [ ] Confirm the build actually RUNNING, not just what the changelog
      says: 19.2.8 / 19.3.5 / 19.4.2 or later.
  [ ] If self-managed, review sign-up restrictions: is self-registration
      open, and is admin approval of new accounts actually enabled?
  [ ] Review who can create or push to a PUBLIC project on your instance
      -- that setting is the other half of this bug's real exposure.
  [ ] Enable and watch GitLab's six named threat detections for this CVE,
      especially "ci lint workhorse worker crash" and "ci regexp guard
      rejection".
  [ ] Check CI-lint access logs for requests with no auth header paired
      with recently-pushed branches on public projects -- that pairing
      is this chain's fingerprint, a bare HTTP 502 is not.

A CVSS score grades the bug. It doesn’t grade your instance.

CVE-2026-97229 is a real critical bug, and GitLab’s and CISA’s severity calls are both right. What the number alone can’t tell you is whether your instance is the wide-open kind this chain was built for, or the kind where registration is closed and project visibility is locked down. That second instance is a meaningfully smaller target, even for a CVSS 10.0, actively-exploited bug. Severity scores a component. Exposure is a property of your configuration, and nobody scores that for you.

That’s exactly the gap I look for in a web application or CI/CD pipeline review: not just ‘is this patched,’ but ‘given how this instance is actually configured, what does the unauthenticated half of a given bug actually buy an attacker here.’ If you run GitLab self-managed, or any CI/CD surface with public-facing pieces, and you’d rather have someone walk that boundary with you than take a CVSS score’s word for it, that’s the kind of assessment I do. Tell me how your instance is actually configured, and we’ll scope what matters.

Sources

Need this checked in your own product?

Web & API penetration testing

Want to learn to find vulnerabilities like this yourself?

Web application penetration testing course, one to one
All articles

08Contact

Shipping something that handles user data?

Send me a short brief. I reply within three days with questions or a proposal outline, and the price is agreed in writing before work starts.