# Rapid7 Analysis: GitLab CI/CD Regex Integer Overflow (CVE-2026-97229)

Integer overflow in GitLab CI/CD regex quantifier expansion lets crafted repo path and CI config run arbitrary commands as the git user via unauthenticated CI lint.

- Payout: Research
- Severity: critical
- Program: GitLab
- Credited: Jonah Burgess
- Disclosed: 2026-10-16
- CVE: CVE-2026-97229
- Write-up quality: 10/10
- Original: https://www.rapid7.com/blog/post/ra-gitlab-ci-cd-regex-integer-overflow-technical-analysis-cve-2026-97229
- Library page: https://bugbountylibrary.com/r/blog%3Arapid7-com-blog-post-ra-gitlab-ci-cd-regex-integer-overflow-technical-analysis-cve-2026-97229
- Tags: domain:web impact:internal-access impact:rce kind:research platform:threat-intel sev:critical tech:ruby technique:parser-differential technique:source-review vuln:auth-bypass vuln:memory-corruption vuln:rce

**Target:** GitLab CI/CD regex matcher (Onigmo) via /api/v4/projects/:id/ci/lint
**Root cause:** Onigmo's quantifier expansion size check multiplies body length by repeat bound in signed int, overflowing negative and bypassing the expand limit; GitLab's repeat scanner misses bounds split by inline (?#) comments, and UTF-8 recompilation of POSIX word classes produces an oversized pcode program outside the timeout.
**Steps:**
1. Push a public project branch containing .gitlab-ci.yml with a conditional include whose rules:exists:regexp uses `{5(?#)7805}` to hide the bound from GitLab's repeat scanner
2. Craft the repeated body from six `[[:word:]]` classes plus character classes whose bitmaps encode attacker pcode, sized so body×bound overflows INT_MAX (37,151×57,805=0x800074d3)
3. Add a repository path containing valid four-byte UTF-8 chars to force Ruby to recompile the regex for UTF-8, triggering the oversized pcode build
4. Send unauthenticated `GET /api/v4/projects/<id>/ci/lint?content_ref=<payload-ref>` to run the static linter against the crafted commit
5. Controlled pcode redirects the backtracking stack into the path, pivots via FinishCode to libruby system(), and overwrites match_at()'s return slot to execute the command as git
**How found:** Rapid7 read GitLab's regex wrapper and Onigmo compiler, noticed the repeat scanner only matches contiguous digits and the size check uses signed multiplication, then hypothesized an inline (?#) comment could split the bound and a UTF-8 subject could force an unchecked recompile.
**Exploitation:** Attacker needs write access to a public project to push the malicious CI config and UTF-8 path, then triggers evaluation unauthenticated via the CI lint API content_ref parameter. The overflowed quantifier expansion emits 57,805 copies; the alternation OP_JUMP displacement lands in a controlled character-class bitmap, which redirects the backtracking stack into the repository path, recovers a libruby pointer from FinishCode, adjusts it to a system() call site, and overwrites match_at()'s return slot. Command runs synchronously in a Puma worker; the worker crashes and Workhorse returns 502, with callback output as success signal.
**Escalation:** Chains CI config injection + regex scanner bypass + UTF-8 recompilation + pcode overflow into ASLR-stable native code execution as the git OS account.
**Insight:** When a regex wrapper validates source length and repeat bounds, look for parser differentials between the wrapper's scanner and the real engine (inline comments, escapes) and for encoding-triggered recompiles that bypass size checks done on the original program.
