Alpine's GitLab Incident: Patch the Runtime, Not Just the App

Alpine upgraded GitLab past the fix for CVE-2026-97229 and stayed exposed, because the bug lived in Ruby's regex engine. What self-hosters should check.

Alpine's GitLab Incident: Patch the Runtime, Not Just the App
In this essay

Alpine Linux published a post-mortem on 28 October 2026 about attacks on its self-hosted GitLab, and it matters for a reason beyond the CVSS 10.0 score. It shows that when you supply the runtime yourself, a vendor's security patch may never reach the code that is actually vulnerable.

A maximum-severity bug, then a real attack

CVE-2026-97229 is an unauthenticated remote code execution flaw rated CVSS 10.0 and classified as an integer overflow (CWE-190). It was reported through GitLab's HackerOne bug bounty program by the researcher joaxcar. On 12 October, GitLab released fixed versions 19.2.8, 19.3.5 and 19.4.2 for its Community and Enterprise editions. GitLab.com and GitLab Dedicated were patched by the vendor and need no customer action.

Eight days later, on 20 and 21 October, an attacker created new accounts on Alpine's GitLab instance and submitted malicious CI configuration. Alpine is not a random target. Its packages sit underneath a huge number of container images, so its build infrastructure is attractive to anyone hoping to poison a software supply chain.

The bug lived below GitLab

A brass key inside a small steel safe on a walnut desk, set apart from a closed laptop, illustrating signing keys kept separate from the build server

The CVE carries GitLab's name, but the faulty code sits in Onigmo, the regular expression engine built into the Ruby runtime.

GitLab lets users write pipeline rules in .gitlab-ci.yml that use regular expressions, for example to decide which branches, tags or triggers should start a job. The server compiles those patterns. Under certain conditions, including paths that contain particular UTF-8 sequences, compiling an untrusted pattern overflows an integer inside Onigmo. The overflow corrupts memory and can let an attacker run code on the host as the git service account.

On a self-managed instance with open self-registration or public project creation, the attacker needs no privileged access first. A freshly created account is enough to reach the CI parser.

The architectural lesson is uncomfortable. A feature designed only to decide whether a job should run passed attacker-controlled text into a low-level C component that GitLab's own engineers did not write.

Why upgrading GitLab did not close the hole

This is the most useful part of the report. On 22 October, Alpine upgraded to GitLab 19.3.6, a version newer than the fixed 19.3.5. According to the post-mortem, the instance was still exposed.

The reason is packaging. GitLab's official Omnibus packages and container images bundle their own Ruby, and that bundled Ruby is where the fix landed. Alpine ran GitLab on the distribution's standard system Ruby, so a newer GitLab version did not change the Onigmo code actually executing. Upstream Ruby released its fixes only on 23 October, in versions 3.3.13, 3.4.12 and 4.0.9, about eleven days after GitLab's own release.

Anyone who builds GitLab from source, or packages it on a custom base image, falls into the same gap. The version dashboard says “patched” while the process in memory is not.

A one-line check belongs in your runbook. Run it inside the GitLab container or on the host, in the same environment GitLab actually uses:

ruby -e 'puts RUBY_VERSION'

The result should be at least 3.3.13, 3.4.12 or 4.0.9 for its branch. Anything older means the GitLab upgrade alone did not protect you.

The general rule is simple: a security fix belongs to a component, not to a product name. You need to know which component carries the fix and whether your build really contains it.

What limited the damage: keys that never touched the Git server

The incident could have been far worse. Malicious Alpine packages carrying valid signatures would have flowed downstream into countless containers. That risk was contained because Alpine's package signing keys were stored completely separately from the GitLab server.

Many teams, including plenty of Vietnamese startups, keep release-signing keys or cloud deploy credentials as CI/CD variables because it makes automated releases convenient. In that setup, a compromised Git server also means a compromised release channel, along with every secret, CI variable and runner token stored on it.

Alpine separated where code is built from where artifacts are signed. It still treated the machine as untrusted and rebuilt it from scratch. That was an expensive step, but a far smaller one than an ecosystem-wide breach.

A checklist for teams running their own GitLab

If you operate a self-managed GitLab, the Alpine report suggests a concrete order of work:

  • Patch both layers. Upgrade GitLab to 19.2.8, 19.3.5, 19.4.2 or later, then confirm that the Ruby it runs is fixed. Official packages carry the fix; custom images must be rebuilt on a patched Ruby.
  • Inventory runtime components. Your image's bill of materials should list the interpreter and native libraries, not only the application version.
  • Close open sign-up and restrict free project creation for accounts outside your organization.
  • Gate CI execution from unreviewed accounts and forks.
  • Rebuild when you see probing. Treat signs of exploitation as a reason for a clean rebuild, not a quick patch.
  • Keep signing keys off CI and move signing into a separate, minimal step.

Self-hosting GitLab remains a reasonable choice for data control and cost. The trade-off is that you become the integrator of every layer beneath it, not just a GitLab customer. Teams that cannot track their runtime as closely as their application are safer on the vendor's official packages.

Sources

  1. news.ycombinator.com
  2. cve.org
  3. rapid7.com
  4. alpinelinux.org
  5. orca.security
  6. nvd.nist.gov

AI-assisted · Reviewed by PKN

0

Responses

Loading comments…