On July 24, 2026, security research group depthfirst published a working proof-of-concept exploit for self-managed GitLab. An authenticated user with permission to push to a project can use it to execute arbitrary commands as the server's git account. The attack requires no administrator privileges, CI runner access, or action by another user.
The entry point is a crafted Jupyter notebook committed to a repository. The attacker then opens its commit diff, causing GitLab to process the malicious file.
GitLab released patches on June 10, about six weeks before the exploit became public. According to the reporting, the releases described the change as a bug fix for notebook diff rendering, without a CVE, CVSS score, or security advisory. As of July 26, that classification is a central concern: teams that prioritize security releases may have deferred a patch for remote code execution.
How notebook rendering leads to command execution
GitLab uses an in-tree gem called ipynbdiff to produce readable diffs for .ipynb files, the JSON-based format used by Jupyter notebooks. When a commit changes a notebook, ipynbdiff calls Oj, a Ruby JSON parser backed by native C code, to check that the document contains a "cells" field.
The researchers chained two memory corruption bugs in that parser:
- A nesting stack overflow in
Oj::Parser.usual.parse. Oj allocates a 1,024-byte nesting stack on the heap to track JSON structure depth. Deeply nested objects can cause writes beyond that allocation, allowing an attacker to overwrite the parser's internalstartcallback pointer. - Unsafe narrowing of key lengths to a signed 16-bit field. A sufficiently long object key has its length truncated. In the reported example, a 65,565-byte key becomes a length of 29 bytes. The mismatch lets the attacker leak a heap pointer through the rendered diff returned to the browser.
The exploit uses an initial notebook commit and diff request to prepare memory and extract pointer information. That information helps locate libc, the system's C library, defeating address-space layout randomization, or ASLR. ASLR makes memory addresses harder to predict, so locating the library is an important step toward redirecting execution.
According to the researchers, locating libc takes five to ten minutes on a fresh GitLab installation. On a server that has been running for months, the process can take one to two hours because the heap is more fragmented.
A second and third notebook commit then trigger the payload. The exploit redirects the start callback to system(), which executes commands as the git account used by GitLab's Puma web workers.
That account has broad access within the application. The reported exposure includes repository source code, Rails application secrets such as database credentials, encryption keys and session secrets, CI/CD pipeline data, stored artifacts, and integration credentials. An attacker can also attempt to reach internal services accessible from the GitLab host. The consequences can therefore extend beyond the repository used to deliver the notebook.
The patch arrived before a clear security warning
The reported disclosure timeline was relatively short:
- May 21: The Oj bugs were reported to the gem's maintainer.
- May 27: Fixes were merged.
- June 4: Oj 3.17.3 was released.
- June 5: The GitLab-specific exploit chain was reported to GitLab.
- June 8: GitLab confirmed the issue.
- June 10: GitLab released patched versions 18.10.8, 18.11.5, and 19.0.2.
- July 24: depthfirst published its working proof of concept.
The coordination between the researchers, the Oj maintainer, and GitLab produced a fix promptly. The problem was how that fix was presented. Release notes reportedly described stability improvements to notebook diff rendering rather than identifying a security vulnerability.
That distinction affects patch decisions. A team may install security updates quickly while scheduling ordinary bug fixes for a later maintenance window. If the vendor doesn't identify the security impact, the team lacks the information needed to choose between those paths.
The absence of a CVE or advisory can also leave a gap in automated checks that depend on those records. It doesn't establish that every scanner missed the problem, but CVE-based prioritization alone would not explain the risk. By the time the exploit became public, the patched releases had been available for six weeks without the reported security warning.
The disclosure decision deserves criticism even though the patch itself arrived quickly. A fix for an authenticated command-execution vulnerability should give administrators enough information to assess its urgency. Describing it only as a rendering stability fix obscures that decision.
An old native dependency inside a Ruby application
The Oj bugs had reportedly existed for nearly five years before depthfirst found and developed an exploit for them. GitLab ships Oj as a bundled transitive dependency, meaning it is included through another component rather than necessarily being a dependency administrators choose directly.
This matters because the parser handles untrusted repository content inside a web worker. Although GitLab is a Ruby application, the affected parsing code is native C. Memory corruption in that code occurs within the same process as the application, with access to its memory and operating-system permissions.
As the report on the public exploit illustrates, a dependency inventory is only part of the answer. It can identify an installed gem and its version. Without a known vulnerability record or another security signal, it cannot establish that a supported version safely handles hostile input.
The same concern applies to Ruby, Python, or Node applications that call native extensions for speed. Parsers and codecs for JSON, YAML, XML, images, and compressed data can have substantial input-handling complexity. Their age or performance reputation doesn't establish memory safety. This case required research into the parser's behavior rather than a routine check against a list of known CVEs.
Affected versions and available fixes
The reported affected ranges cover GitLab Community Edition and Enterprise Edition:
| Affected GitLab CE/EE versions | Patched release |
|---|---|
| 15.2.0 through 18.10.7 | 18.10.8 |
| 18.11.0 through 18.11.4 | 18.11.5 |
| 19.0.0 through 19.0.1 | 19.0.2 |
GitLab 15.2 through 18.9 are outside the current patch train, and no backports are planned for those versions. Installations older than 18.10 need an upgrade to a current patched release rather than a hotfix for their existing branch. The table identifies fixed versions, not a complete upgrade procedure for older installations.
The published proof of concept specifically targets x86-64 GitLab 18.11.3. The researchers say other builds on the same architecture require minor offset updates, while ARM64 requires substantial rework. It is therefore not a ready-to-run exploit for every affected installation. However, the reported changes needed for other x86-64 builds are limited for someone experienced in memory exploitation.
There is no official GitLab workaround in the reporting. Restricting project push access to trusted accounts reduces exposure, but it doesn't remove the vulnerability. A multi-tenant installation where untrusted developers can push code warrants an emergency patch assessment.
GitLab.com, the hosted service, is reported as unaffected. The affected-version guidance here concerns self-managed installations.
Checks for affected installations
- Confirm the installed version. Run
gitlab-rake gitlab:env:infoor check Admin > Dashboard. Compare the result with the affected ranges above. - Review recent notebook commits. Investigate unexpected additions or changes to
.ipynbfiles, especially from accounts that would not normally submit notebooks. The exploit requires crafted notebook commits, but the presence of a notebook alone is not evidence of compromise. - Look for unexpected activity under the
gitaccount. Review available GitLab logs and process history for unusual commands or processes associated with Puma workers. The payload usessystem()from a web worker, so unexplained child processes deserve attention. - Upgrade to a patched release. After the upgrade, run
gitlab-rake gitlab:checkto check the installation. - Assess credential exposure. If untrusted users had access, consider precautionary rotation of potentially exposed Rails secrets and service credentials. Rotation needs planning, especially for encryption keys, rather than being treated as a harmless blanket change.
The coverage of the GitLab vulnerabilities and disclosure also raises questions about the company's security advisory process. The reported lack of a security classification, including after the proof of concept became public, left administrators without a clear vendor signal for prioritizing the fix.
Affected self-managed installations need to be upgraded promptly. Patch policies that rely entirely on CVEs and vendor security labels also need a way to review security-relevant dependency and parser changes when those labels are missing.