// threat intel

Alpine Linux GitLab Attack: Upgrades Didn’t Fix the Ruby Bug

Alpine Linux GitLab attack: CVE-2026-97229 Ruby Onigmo flaw survived GitLab upgrades on gitlab.alpinelinux.org

In the Alpine Linux GitLab attack, an unknown attacker spent about a week in October 2026 trying to exploit CVE-2026-97229 on gitlab.alpinelinux.org, the server hosting aports, the repository every Alpine package is built from. The CVSS 10.0 flaw sits in Ruby's regex engine. Two GitLab upgrades did not fix it, because GitLab patched the Ruby it bundles and Alpine ran its own.

Key takeaways

  • An unknown attacker targeted gitlab.alpinelinux.org, which hosts Alpine's aports build repository, with CVE-2026-97229 (CVSS 10.0) from 20 to 26 October 2026.
  • The flaw is in Onigmo, Ruby's regex engine, reached through regular expressions in CI configurations. GitLab's 12 October fix patched only the Ruby it bundles.
  • Alpine's separately installed Ruby stayed vulnerable after upgrades to 19.3.4 and 19.3.6. A new account crashed workers eight times, getting partway through the exploit in four.
  • Alpine found no sign of code execution but lost background job logs and the old filesystem, so it rebuilt the server, replaced secret keys, moved to Ruby 3.3.13 or later and reset all passwords.
  • Packages built 20-24 October and the 21 October releases, including Docker minirootfs, were verified against a developer's copy and all 45 build servers. Checks are being extended to 27 October.
  • Self-compiled or own-Ruby GitLab installs need Ruby 3.3.13, 3.4.12, 4.0.9 or later in addition to the GitLab upgrade, and Ruby 3.2 will get no fix.

What happened in the Alpine Linux GitLab attack

Alpine Linux disclosed on 28 October 2026 that an attacker had tried to exploit CVE-2026-97229 on its own GitLab server, starting a week after GitLab released a fix. Alpine found no evidence the attack worked but cannot rule it out, so it rebuilt the server from scratch and reset every account’s password.

The target matters. gitlab.alpinelinux.org hosts aports, the repository Alpine’s packages are built from, which makes it the entry point of the distribution’s supply chain. Alpine’s timeline:

  • 12 October: GitLab releases its fix, patching the Ruby inside its Linux package and official container images.
  • 13 October: Alpine upgrades to GitLab 19.3.4 instead of 19.3.5, which it calls a mistake.
  • 20-21 October: A newly registered account sends crafted CI configurations. GitLab worker processes crash eight times. The main attack on 21 October also causes 502 errors and failed CI jobs.
  • 22 October: Alpine upgrades to 19.3.6. Its Ruby stays vulnerable.
  • 23 October: Alpine blocks the account.
  • 25-26 October: The attacker returns and fails to get back in.
  • 26 October: Alpine finds the server is still vulnerable.
  • 27 October: GitLab is closed to everyone except developers and build servers, and the rebuild starts.
  • 28 October: Rebuild complete, with new GitLab secret keys and Ruby 3.3.13 or later. Alpine is testing it before reopening it to the public.

Two details show a patient operator. In four of the eight crashes, the attacker got partway through the exploit and failed at the next step. The attacker also downloaded Alpine’s GitLab container image, which Alpine said “suggests they were tuning the exploit to our exact build.”

Why did GitLab 19.3.6 not fix CVE-2026-97229?

Because the bug is not in GitLab’s code. It lives in Onigmo, the regular-expression engine built into Ruby. GitLab compiles the regular expressions in CI configurations with Ruby, which lets a pipeline file feed input straight into native code that no GitLab-level validation inspects.

GitLab’s 12 October fix patched the Ruby inside its Linux package and official container images. If you run those artifacts, the Ruby fix arrives with the GitLab upgrade. Ruby’s own fixed releases, 3.3.13, 3.4.12 and 4.0.9, came out later, on 22 and 23 October.

Alpine’s alpinelinux/gitlab image installed a standard Ruby separately. A GitLab upgrade does not touch that interpreter, so the 22 October move to 19.3.6 put a current version string on top of the same vulnerable Onigmo. The admin page looked patched. Alpine worked this out on 26 October, while the attacker was trying to get back in.

The 19.3 branch had a second trap. When GitLab published its advisory on 12 October, it named 19.3.4 as the fixed release for that branch. It now names 19.3.5 and lists 19.3.4 as affected. Any server that stopped at 19.3.4, as Alpine’s did on 13 October, should move to 19.3.6.

GitLab’s documentation does not close the gap either. As of 29 October, neither its 12 October nor its 21 October patch release post tells administrators of self-compiled installs to update Ruby. Its self-compiled installation guide still lists Ruby 3.2 as the minimum. Ruby 3.2 reached end of life in April and will get no fix.

Application fixes and runtime fixes travel through different channels, often owned by different teams. A ticket that says “upgrade GitLab” closes on a version check while the vulnerable library stays put, and a fixed library on disk does nothing until Puma and Sidekiq restart. If GitLab runs on a Ruby your platform team installs, that team owns CVE-2026-97229, and the GitLab service owner needs to know it.

We have seen a GitLab config that becomes code execution before. Pipeline and flow configuration keep turning into execution surfaces, and they deserve the same review as code.

Who can actually reach the vulnerable code?

Anyone who can get a crafted regular expression into a CI configuration, and how hard that is depends on the server’s settings. GitLab’s advisory says that “under certain conditions” the flaw could let an unauthenticated user run code on the server. GitLab.com and GitLab Dedicated are already patched, so the exposure is self-managed installs.

Rapid7 built a working exploit in a lab and published proof-of-concept code on 13 October. It ran commands as GitLab’s git user on an unpatched official GitLab 19.4.1 image. The same input did not run on the patched 19.4.2 image.

Rapid7’s route needs write access first. The attacker must commit a malicious .gitlab-ci.yml file and a file path containing UTF-8 characters to a branch of a public project. In Rapid7’s words, “only the final CI lint request is unauthenticated.” GitLab’s detection notes describe the same prerequisite.

Rapid7 does not yet know how the attack in the original report to GitLab, which is still private, avoids that write step. On a server where new users can register and create projects, though, the write requirement is a formality.

Alpine’s attacker was a newly registered account. Alpine has not said how the attacker’s CI configurations reached GitLab, or whether new accounts could create public projects on its server. Readers should not assume either answer.

Ruby’s maintainers add two scoping notes. The bug matters only for programs that compile a regular expression from text an attacker controls, and Ruby’s Regexp.timeout setting does not protect against it. CVE-2026-97229 was not in CISA’s Known Exploited Vulnerabilities catalog as of the catalog’s 28 October update.

What would a successful exploit have left behind?

Probably nothing Alpine could see. “A successful exploit would leave no crash and no trace in the web logs,” Alpine said. The eight crashes record attempts that failed. An attempt that worked would look like an ordinary request with an ordinary response.

Alpine looked anyway. Timing probes showed no delays. It found no unexpected files, keys, hooks or processes. No private data went back to the attacker through any web or API request Alpine can see.

The gaps sit exactly where confirmation would come from. Some logs from the attack day are gone, including background job logs, and so is the old container’s filesystem. Neither Alpine nor its hosting provider logged outbound network connections. With that evidence missing, Alpine treated the server as compromised and rebuilt it to remove any foothold.

That was the defensible call. A compromised GitLab compromises source code, CI secrets and the path to production at once. If the attack succeeded, Alpine says these could have been exposed:

  • Email addresses, names and IP addresses of all users
  • Password hashes
  • 2FA secrets, CI/CD variables and runner tokens
  • Webhook, integration and push mirror credentials
  • Personal access tokens, which are stored as hashes
  • Private repositories, plus confidential issues and merge requests

Alpine has reset passwords and replaced GitLab’s secret keys. It has not said whether runner tokens, CI/CD variables, access tokens or integration credentials stored on the server were replaced.

Each of those can unlock systems outside GitLab: cloud accounts, registries, mirrors. Each should remain an open item until someone has explicitly verified it. New keys and passwords do not prove the attacker failed. They make any attacker success worth less.

Did the attack reach Alpine packages or Docker images?

Alpine says no for the window it has checked so far. Packages built from 20 to 24 October, and the 21 October releases, came from aports changes verified against records outside GitLab. Those releases include the minirootfs files that the official Alpine Docker images are built from.

The method is the strong part. Alpine confirmed that every aports change added from 20 to 22 October arrived through a recorded push by a known developer. It checked that against records it says the attacker could not reach: a developer’s copy of the repository and all 45 build servers. Every package built from 20 to 24 October came from aports as published on git.alpinelinux.org.

None of that evidence depends on the suspect host. If someone had slipped a change into aports through GitLab, the server’s repository would differ from the developer’s copy, or a builder would hold source that no known developer pushed. Checking all 45 builders, rather than a sample, removes the case where one builder fetched something odd and nobody looked.

The window does not yet cover the whole exposure. The attacker returned on 25 and 26 October, and the server stayed open until 27 October. Alpine says it is extending its checks to 27 October, the day it closed the server. Until it reports the result, treat 25-27 October as unverified rather than clean.

Signing has a cleaner answer. The keys that sign Alpine’s packages and releases have never been on the GitLab server. An attacker on that host could, in principle, have targeted build input, which is what the provenance checks address. They could not have taken the signing keys from it.

What should a self-hosted GitLab operator do this week?

Upgrade, then prove that the Ruby your GitLab processes actually run is fixed. Which release closes the hole depends on how GitLab is installed:

  • GitLab’s Linux package or official container images: upgrade to the newest release for your branch. The patched Ruby comes with it.
  • Self-compiled installs, or images that install their own Ruby: upgrade GitLab and update Ruby to 3.3.13, 3.4.12, 4.0.9 or later. Servers on Ruby 3.2 must move to one of those versions, because 3.2 will not get a fix.

GitLab’s 21 October releases are the newest on each branch, and they also fix CVE-2026-110264, a separate regex flaw.

Branch Fixes CVE-2026-97229, per GitLab’s advisory Newest release (21 October)
19.5 Not listed as affected 19.5.1
19.4 19.4.2 19.4.3
19.3 19.3.5 19.3.6
19.2 19.2.8 19.2.9
19.1, 19.0 and 18.11 Not listed as affected 19.1.9, 19.0.10 and 18.11.13

Check the runtime, not the version page

When we test self-hosted GitLab, the version on the admin page and the runtime inside the processes disagree more often than teams expect. Typical causes: a custom image on a distro base, a Ruby pinned by a platform team that never saw the advisory, or one Sidekiq node on last month’s image.

  • From the GitLab application environment, run gitlab-rails runner ‘puts RUBY_DESCRIPTION’ and compare the result with the fixed Ruby versions. Do it on every web and Sidekiq node or container, not one.
  • On Alpine-based deployments, run apk info -v ruby inside the image GitLab actually uses, not on the host.
  • After updating, check process start times with ps -eo pid,lstart,args and confirm Puma and Sidekiq restarted after the runtime changed.

If you cannot upgrade today

Rapid7 suggests restricting anonymous access to public projects, stopping untrusted users from creating or writing to public projects, or blocking the CI lint route. Rapid7 is explicit that these steps cover only its exploit route and do not fix the vulnerable regex compiler. GitLab’s guidance adds: limit who can create accounts and personal access tokens.

Detect and hunt

GitLab has published six Sigma detection rules for self-managed servers. Four, marked experimental, look for hostile patterns in CI regexp warnings and GraphQL ciLint requests, unusually long file paths with non-ASCII characters, and Ruby crash reports pointing to Onigmo. GitLab’s guidance repeats Alpine’s warning that a successful exploit can leave no crash, and lists further checks:

  • Files owned by the git user in /tmp.
  • Unexpected child processes of Puma or Sidekiq.
  • Outbound connections from the GitLab host.
  • Changed crontabs or SSH keys for the git user.

If you find signs of code execution, treat the server as compromised and rotate the secrets in gitlab-secrets.json, all CI/CD variables, all runner tokens and all access tokens. Preserve container filesystems and logs before you rebuild, and ship logs off the host now. Alpine’s investigation stalled on exactly that evidence.

If your GitLab runs from custom images, a container security assessment that matches runtime libraries against the processes loading them closes this class of gap.

What should Alpine users and image consumers do?

Account holders need a new password. When gitlab.alpinelinux.org reopens, use “Forgot your password?” on the sign-in page. Alpine has also seen automated login attempts using passwords leaked from other sites, so anyone who reused their gitlab.alpinelinux.org password elsewhere should change it there too.

Users of the alpinelinux/gitlab image should pull alpinelinux/gitlab:19.3.6 again. The image published on 27 October contains Ruby 3.3.13. Any 19.3.6 image pulled before then, and every older tag, is vulnerable.

Alpine’s own Ruby packages already carry the fix: Ruby 3.4.12 on Alpine 3.22, 3.23, 3.24 and edge, and Ruby 3.3.13 on Alpine 3.21. Running apk upgrade installs it.

For teams building on Alpine base images, the public evidence does not justify pulling every image built that week. Pin images by digest, keep an inventory of Alpine-based images built in late October, and watch for Alpine’s report on the 25-27 October extension. When you assess any supplier after an incident, ask for what Alpine provided: who pushed, which builders built, and which off-host copy the output was checked against.

What is still unknown

  • Who the attacker was and what they wanted from Alpine’s build infrastructure.
  • Whether any attempt achieved code execution. With logs and the old filesystem gone, that may never be answered.
  • How the attacker’s CI configurations reached GitLab, and how the private original exploit avoids Rapid7’s write step.
  • Whether runner tokens, CI/CD variables, access tokens and integration credentials were rotated.
  • How many other self-hosted GitLab servers run a separately installed Ruby and still carry the vulnerable Onigmo behind a current version string.

That last unknown is the one most readers can resolve today, with one command per worker.

FAQ

What is CVE-2026-97229?

It is a CVSS 10.0 vulnerability in Onigmo, the regular-expression engine built into Ruby. GitLab compiles regular expressions from CI configurations with Ruby, so a crafted CI configuration can lead to code execution on the server. GitLab says that under certain conditions an unauthenticated user could exploit it.

Is CVE-2026-97229 in GitLab or in Ruby?

In Ruby. GitLab fixed it on 12 October by patching the Ruby inside its Linux package and official container images. Ruby's fixed releases (3.3.13, 3.4.12 and 4.0.9) followed on 22 and 23 October. Self-compiled installs and images with their own Ruby need both a GitLab upgrade and a Ruby update.

Was Alpine Linux's GitLab hacked?

Alpine found no evidence that the attack succeeded but cannot rule it out. A successful exploit would leave no crash or web-log trace, and some logs plus the old container filesystem are gone, so Alpine treated the server as compromised, rebuilt it and reset every password.

Did the Alpine GitLab attack affect official Docker images?

Alpine says no for the window checked. Packages built from 20 to 24 October and the 21 October releases, including the minirootfs files behind the official Docker images, were verified against a developer's repository copy and all 45 build servers. Alpine is extending the checks to 27 October.

Which GitLab versions fix CVE-2026-97229?

GitLab's advisory lists 19.4.2, 19.3.5 and 19.2.8 as fixed, and the newest releases are 19.4.3, 19.3.6 and 19.2.9. Servers on 19.3.4 are affected. Those releases fix the flaw only when GitLab runs its own bundled Ruby or a fixed system Ruby.

How do I check whether my GitLab runs the patched Ruby?

Run gitlab-rails runner 'puts RUBY_DESCRIPTION' on every web and Sidekiq node or container and compare it with Ruby 3.3.13, 3.4.12 or 4.0.9. Then confirm Puma and Sidekiq started after the runtime was updated.

If your GitLab, runners and image pipeline have never been tested from an attacker’s seat, agent::offense can map what a foothold on them would reach. See our cloud and container security testing.


// get started

Work with AgentOffense

Tell us about your target and goals. We’ll reply with scope and a fixed-price quote — usually within one business day.

./request_engagement