CI Config Risk: How a Regex in a YAML File Can Take Down a Server
By Arash Latifi
CVE-2026-97229 was exploited from inside a CI config file. A technical look at the attack chain, Onigmo's role, and how to harden your own CI against hostile input.
TL;DR — This week's attack on Alpine's GitLab started inside a CI config file: a crafted regex triggered an integer overflow in Ruby's regex engine and escalated to RCE. If your CI compiles user input, read this.
The attack chain, step by step
Based on Alpine's disclosure and Ruby's official advisory, here's how CVE-2026-97229 worked:
- GitLab lets users define regexes in CI config (for
rules, policies, etc.). These are compiled withRegexp.new— user input goes straight into the regex compiler. - Ruby's regex compiler (Onigmo) had an integer overflow on specific patterns. Two bugs (
CVE-2026-93577,CVE-2026-97229) chained into RCE, plus a separate double free (CVE-2026-89078). - The unauthenticated attacker sent crafted CI configs; each compile crashed a worker. Alpine logged 8 crashes — the attacker got partway through the exploit in 4 of them.
A key technical detail from the HN discussion: the exploit wasn't just the overflow. It combined "regexes that produce extremely long bytecode + integer overflow + double free." That's why Ruby had to fix all four bugs at once in 3.3.13 / 3.4.12 / 4.0.9.
Why patching GitLab wasn't enough
GitLab shipped the fix on October 12 (19.4.2, 19.3.5, 19.2.8) — but the fix was a patch to the bundled Ruby, not a change in GitLab's code. Anyone running standard Ruby from source or a custom image got nothing. Alpine discovered on October 26 that their upgrade to 19.3.6 was still vulnerable.
Quick check on your own server:
If you run GitLab with your own Ruby (source or custom image), don't count the GitLab upgrade alone:
The general lesson: user input + compilation = danger zone
GitLab's problem wasn't unique. Anywhere untrusted input goes into a compiler, this pattern repeats:
| Rule | Why |
|---|---|
| Compile early, reject early | Validate and compile user regexes at save time, not on every pipeline run |
| Don't trust the runtime patching story | The fix may live in the underlying language (here Ruby), not your app code |
| No floating tags | ruby:3.4 and latest can stay vulnerable for weeks; pin exact versions |
| Log worker crashes | 8 crashes in a row with similar input was Alpine's first attack signal |
| Isolate CI | Config-parsing workers and runners should have the least privilege possible |
CI hardening checklist
- [ ] Reject user-supplied regexes in CI config at save time with validation tests.
- [ ] Pin runtime versions (Ruby/Node/Python) in CI images; add a weekly bump job.
- [ ] Monitor worker logs for repeated crashes with similar input.
- [ ] Subscribe to your vendor's security feed — Alpine was attacked a week after the fix shipped.
- [ ] Write your "can't rule it out → rebuild" plan in advance; Alpine moved fast because they had practiced it.
FAQ
Q: We don't run CI, but we have nginx/Node/PHP — what do we do?
Same principle: figure out whether security fixes land in your app's code or in the underlying runtime. Recent incidents (PHP2Shell, this Onigmo bug) show the lower layer is the main target. Manage runtime versions like a first-class dependency.
Q: How do I know if my users' regexes are dangerous?
Anywhere you have the equivalent of new RegExp(userInput) in any language, there's risk — even without RCE, ReDoS from nested patterns is common. At minimum: cap length, enforce step/time limits, or whitelist allowed patterns.
Q: We installed GitLab 19.4.2 — are we safe?
If you use GitLab's official packages or images and pulled recently, yes — the Ruby patch is included. If you carry your own Ruby, check embedded/bin/ruby or your system Ruby: at least 3.3.13 / 3.4.12 / 4.0.9.