← All hunts high TLP:CLEAR

GitLab CI/CD Regex Compiler RCE Detection

An attacker exploited CVE-2026-97229 by planting a malicious .gitlab-ci.yml in a repository, causing an integer overflow in the Ruby Onigmo engine that executes arbitrary commands as the git user.

Based on research by Rapid7 2026-10-14 11 steps · 4 queries T1059 T1190

Brief

Why now

Rapid7 recently published a report on CVE-2026-97229 (https://www.rapid7.com/blog/post/etr-cve-2026-97229-critical-unauthenticated-remote-code-execution-in-gitlab), detailing a CVSS 10.0 vulnerability in self-managed GitLab Community and Enterprise editions. This flaw resides in the Ruby Onigmo regex engine used during CI/CD configuration parsing. An unauthenticated attacker can trigger an integer overflow by submitting a crafted .gitlab-ci.yml file, leading to remote code execution as the git user.

How the hunt flows

The hunt begins with a scoping phase using software inventory telemetry. It identifies every self-managed GitLab instance running the affected 19.2, 19.3, or 19.4 branches. This prevents the hunt from processing telemetry for patched or unrelated systems, ensuring the remaining queries focus only on vulnerable infrastructure.

After identifying targets, the hunt searches for delivery evidence through two parallel paths. The first query monitors file activity for writes to .gitlab-ci.yml files, while the second examines web traffic for unauthenticated project creation or IDE session initialization. These queries establish a timeline of configuration changes that could deliver the exploit payload.

The execution phase pivots to process telemetry to find signs of a successful overflow. It looks for rare shell spawns or network utility executions, such as bash, python, or curl, originating from GitLab worker processes like Sidekiq, Ruby, or Bundle. The query uses stack-counting across the estate to highlight commands that appear on three or fewer hosts, filtering out common developer hooks or maintenance tasks.

Finally, an automated agent reviews the findings from all previous steps. It correlates the presence of a vulnerable version with recent delivery indicators and anomalous process behavior. This multi-stage approach ensures that a verdict of malicious activity is based on a complete exploit chain rather than an isolated, potentially benign event.

What the hunt cannot see

This hunt requires endpoint telemetry from an agent installed on the GitLab host. If a server is not enrolled in process and file monitoring, the hunt cannot observe the execution or delivery phases. Additionally, the telemetry used here focuses on file paths and process command lines; it does not include the content of the .gitlab-ci.yml file itself. An analyst must manually inspect the repository contents if the hunt flags a suspicious configuration write to confirm the presence of the malicious regex.

Steps

  1. Identify vulnerable GitLab instances

    Query · scoping

    Narrow the hunt to self-managed GitLab servers running the affected 19.2, 19.3, or 19.4 branches.

    reads hb_software_inventorysql
    SELECT device_hostname, package_name, package_version FROM hb_software_inventory WHERE (LOWER(package_name) LIKE '%gitlab-ce%' OR LOWER(package_name) LIKE '%gitlab-ee%') AND (package_version LIKE '19.2.%' OR package_version LIKE '19.3.%' OR package_version LIKE '19.4.%')

    What a hit looks like. A list of hosts running vulnerable versions. Silence means the estate is patched or does not run self-managed GitLab.

  2. Malicious CI/CD configuration writes

    Query · enrichment

    Find recent writes to the .gitlab-ci.yml file, which is the primary delivery mechanism for the exploit.

    reads hb_file_activitysql
    SELECT device_hostname, file_path, actor_user_name, time FROM hb_file_activity WHERE LOWER(file_path) LIKE '%.gitlab-ci.yml' AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')

    What a hit looks like. A hit identifies the timestamp and actor behind a configuration change. Normal DevOps activity will create noise here.

  3. Suspicious project management traffic

    Query · enrichment

    Identify HTTP requests indicating a new project or IDE session, preceding the planting of the exploit.

    reads hb_http_activitysql
    SELECT device_hostname, src_endpoint_ip, url_path, time FROM hb_http_activity WHERE (LOWER(url_path) LIKE '%/projects/new%' OR LOWER(url_path) LIKE '%/-/ide/%') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')

    What a hit looks like. Rows show IPs initiating project setup. High volumes from external or unknown IPs warrant closer inspection.

  4. Assess delivery risk

    Agent triage

    Determine if any vulnerable host is currently showing signs of exploit delivery.

  5. Rare processes spawned by GitLab workers

    Query · detection candidate

    Identify successful RCE by stack-counting rare shell and utility processes spawned by the Ruby application workers.

    reads hb_process_activitysql
    SELECT LOWER(process_cmd_line) AS cmd, GROUP_CONCAT(DISTINCT device_hostname) AS hosts, COUNT(DISTINCT device_hostname) AS host_count, MIN(time) AS first_seen FROM hb_process_activity WHERE LOWER(user_name) = LOWER('{{git_user}}') AND (LOWER(parent_process_name) LIKE '%ruby%' OR LOWER(parent_process_name) LIKE '%sidekiq%' OR LOWER(parent_process_name) LIKE '%bundle%') AND (LOWER(process_name) LIKE '%/sh' OR LOWER(process_name) = 'sh' OR LOWER(process_name) LIKE '%/bash' OR LOWER(process_name) = 'bash' OR LOWER(process_name) LIKE '%/python%' OR LOWER(process_name) = 'python' OR LOWER(process_name) LIKE '%/curl' OR LOWER(process_name) = 'curl' OR LOWER(process_name) LIKE '%/wget' OR LOWER(process_name) = 'wget') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days') GROUP BY cmd HAVING host_count <= 3

    What a hit looks like. A rare shell command or network utility spawned by a GitLab worker is a high-fidelity indicator of successful RCE. Silence proves the absence of this specific behavior on the GitLab hosts during the window.

  6. Review the complete exploit chain

    Agent triage

    Correlate the delivery indicators with the execution indicators to confirm exploitation.

  7. Route on verdict

    Decision

    Direct the hunt to immediate isolation or manual review based on the agent's confidence.

  8. Isolate and remediate

    Response action

    Halt the attack and bring the server to a secure, patched state.

  9. Analyst forensic review

    Analyst task

    Verify the findings and identify the specific malicious project path.

  10. Close out report

    Analyst task

    Document the hunt results and any remediation taken.

Coverage

Scenario coverage

StageCoveredHow, or why not
Vulnerable GitLab Instance Detection
T1190
Yes vulnerable-gitlab-instances
Malicious CI/CD Configuration Delivery
T1190
Yes config-file-changes, project-creation-traffic
Regex Engine Control-Flow Hijack
T1190 · T1059
Yes rce-behavior-prevalence
Command Execution as git User
T1059
Yes rce-behavior-prevalence

Blind spots

  • Needs hb_process_activity from an endpoint agent. A host without an agent provides no telemetry for the execution phase, leaving a critical blind spot in the detection chain. It would answer whether the GitLab instance is enrolled in monitoring. Remediation: Ensure all GitLab infrastructure is enrolled in the central EDR/monitoring system.
  • Needs Application layer logging of .gitlab-ci.yml content. Surface logs show the file was modified but not the content, requiring the agent to infer maliciousness from the subsequent RCE behavior. It would answer whether the .gitlab-ci.yml specifically contained the overflow regex. Remediation: Enable repository audit logging within GitLab for configuration changes.

Parameters & data

Parameters

ParameterTypeDefaultWhat it is
git_userstringgitThe operating system account that the GitLab service runs as.
lookback_daysnumber14Days of history to examine for file and process activity.
scope_hostslist[host]—Limit hunting to these hosts; leave empty to hunt across the entire estate.

Telemetry

SourceCategoryTelemetry
Endpoint telemetry (hb_ surfaces)endpointendpoint
Web server / proxy logssiemnetwork

Source

Download hunt.md Definition (JSON) An open hunt.md file; it runs anywhere that reads the format.
---
analysis: A single rule on shell spawns by the git user is prone to false positives
  from legitimate hooks. This hunt correlates software version, delivery patterns,
  and execution prevalence using a phased approach and AI triage to reduce noise and
  provide a high-confidence verdict.
blind_spots:
- id: agent-visibility-gap
  owner: IT Operations
  question: whether the GitLab instance is enrolled in monitoring
  remediation: Ensure all GitLab infrastructure is enrolled in the central EDR/monitoring
    system.
  requires: hb_process_activity from an endpoint agent
  risk: A host without an agent provides no telemetry for the execution phase, leaving
    a critical blind spot in the detection chain.
- id: http-body-inspection
  owner: DevSecOps
  question: whether the .gitlab-ci.yml specifically contained the overflow regex
  remediation: Enable repository audit logging within GitLab for configuration changes.
  requires: Application layer logging of .gitlab-ci.yml content
  risk: Surface logs show the file was modified but not the content, requiring the
    agent to infer maliciousness from the subsequent RCE behavior.
  stage: malicious-config-delivery
coverage:
- stage: vulnerable-software-identification
  status: covered
  steps:
  - vulnerable-gitlab-instances
- stage: malicious-config-delivery
  status: covered
  steps:
  - config-file-changes
  - project-creation-traffic
- stage: regex-compiler-exploitation
  status: covered
  steps:
  - rce-behavior-prevalence
- stage: rce-command-execution
  status: covered
  steps:
  - rce-behavior-prevalence
guardrails:
  claims: no_unsupported
  evidence: citation_required
  missing_data: not_benign
  telemetry: untrusted
hunt:
  applicability: campaign-specific
  handoff: promote-to-detection
  justification: CVE-2026-97229 is a critical CVSS 10.0 unauthenticated RCE. This
    hunt provides the necessary business assurance that the critical integer overflow
    has not been triggered on the self-managed GitLab estate.
  methodology: model-assisted
  trigger: intel-report
hypothesis: An attacker exploited CVE-2026-97229 by planting a malicious .gitlab-ci.yml
  in a repository, causing an integer overflow in the Ruby Onigmo engine that executes
  arbitrary commands as the git user.
labels:
- hunt
- attack.t1190
- attack.t1059
- execution
- initial access
name: GitLab CI/CD Regex Compiler RCE Detection
parameters:
  git_user:
    default: git
    description: The operating system account that the GitLab service runs as.
    from:
      kind: manual
      observed: '2026-10-13'
      ref: standard-deployment
    type: string
  lookback_days:
    default: '14'
    description: Days of history to examine for file and process activity.
    from:
      kind: manual
      observed: '2026-10-13'
      ref: hunt-standard
    type: number
  scope_hosts:
    default: []
    description: Limit hunting to these hosts; leave empty to hunt across the entire
      estate.
    from:
      kind: manual
      observed: '2026-10-13'
      ref: analyst-input
    type: list[host]
provenance:
  authors:
  - name: Huntbase hunt generation
    org: huntbase.io
  generated:
    by: huntbase-hunt-generation
    from: https://www.rapid7.com/blog/post/etr-cve-2026-97229-critical-unauthenticated-remote-code-execution-in-gitlab
    gates:
    - dry-run
    - lint
    model: hb_google/gemini-3-flash-preview
rationale: Focus exclusively on self-managed GitLab Community and Enterprise editions.
  GitLab.com is out of scope.
references:
- name: "Rapid7 \u2014 CVE-2026-97229: Critical Unauthenticated Remote Code Execution\
    \ in GitLab"
  url: https://www.rapid7.com/blog/post/etr-cve-2026-97229-critical-unauthenticated-remote-code-execution-in-gitlab
related:
- hunt: gitlab-malicious-registration-monitor
  reason: This hunt focuses on the RCE behavior; monitoring for anomalous spikes in
    new user registrations is a separate sibling hunt.
  relation: out-of-scope-alternative
scenario:
  stages:
  - name: Vulnerable GitLab Instance Detection
    observables:
    - GitLab CE/EE versions 19.2 before 19.2.8
    - GitLab CE/EE versions 19.3 before 19.3.5
    - GitLab CE/EE versions 19.4 before 19.4.2
    - CVE-2026-97229
    slug: vulnerable-software-identification
    tactic: initial-access
    techniques:
    - T1190
  - name: Malicious CI/CD Configuration Delivery
    observables:
    - .gitlab-ci.yml
    - repository path containing UTF-8 characters
    - unauthenticated creation of public projects
    slug: malicious-config-delivery
    tactic: initial-access
    techniques:
    - T1190
  - name: Regex Engine Control-Flow Hijack
    observables:
    - Ruby Onigmo VM operations
    - GitLab application worker processes
    slug: regex-compiler-exploitation
    tactic: execution
    techniques:
    - T1190
    - T1059
  - name: Command Execution as git User
    observables:
    - operating system commands executed as the git user
    - shell processes spawned by Ruby GitLab workers
    slug: rce-command-execution
    tactic: execution
    techniques:
    - T1059
  summary: An attacker exploits an integer overflow in GitLab's CI/CD regular expression
    compiler (CVE-2026-97229) by committing a malicious configuration file and a repository
    path containing UTF-8 characters to a public project. This triggers a control-flow
    hijack in the Onigmo regex engine when evaluating the crafted expression, allowing
    unauthenticated remote code execution as the 'git' user on unpatched self-managed
    instances.
severity: high
targets:
  analyst:
    name: Tier-2 analyst
    role: analyst
  endpoint:
    category: endpoint
    name: Endpoint telemetry (hb_ surfaces)
    telemetry:
    - endpoint
  hunter:
    agent: true
    name: Hunt agent
  web:
    category: siem
    name: Web server / proxy logs
    telemetry:
    - network
tlp: clear
type: investigation
---


# GitLab CI/CD Regex Compiler RCE Detection

This hunt covers the entire lifecycle of CVE-2026-97229 exploitation on self-managed GitLab instances. It begins by scoping the estate to vulnerable 19.2, 19.3, and 19.4 version branches. The hunt then moves through a phased flow: first identifying delivery evidence via configuration file writes and suspicious web activity, followed by a search for the successful execution phase where the GitLab worker process spawns rare shell or utility processes under the git account. An agent weighs the delivery and execution evidence together per host to provide a final verdict.

## vulnerable-gitlab-instances
<!-- Identify vulnerable GitLab instances -->
Narrow the hunt to self-managed GitLab servers running the affected 19.2, 19.3, or 19.4 branches.

```sqlite target=endpoint role=scoping
~~~yaml
expected: A list of hosts running vulnerable versions. Silence means the estate is
  patched or does not run self-managed GitLab.
reads:
- device_hostname
- package_name
- package_version
silence: not_evidence_of_absence
source: hb_software_inventory
verified: dry-run
verified_at: '2026-10-14'
~~~
SELECT device_hostname, package_name, package_version FROM hb_software_inventory WHERE (LOWER(package_name) LIKE '%gitlab-ce%' OR LOWER(package_name) LIKE '%gitlab-ee%') AND (package_version LIKE '19.2.%' OR package_version LIKE '19.3.%' OR package_version LIKE '19.4.%')
```

## parallel-delivery
<!-- Examine delivery evidence -->
parallel:
- → config-file-changes
- → project-creation-traffic
join: → early-delivery-read

## config-file-changes
<!-- Malicious CI/CD configuration writes -->
Find recent writes to the .gitlab-ci.yml file, which is the primary delivery mechanism for the exploit.

```sqlite target=endpoint role=enrichment params=(scope_hosts=scope_hosts, lookback_days=lookback_days)
~~~yaml
expected: A hit identifies the timestamp and actor behind a configuration change.
  Normal DevOps activity will create noise here.
reads:
- actor_user_name
- device_hostname
- file_path
- time
silence: not_evidence_of_absence
source: hb_file_activity
verified: dry-run
verified_at: '2026-10-14'
~~~
SELECT device_hostname, file_path, actor_user_name, time FROM hb_file_activity WHERE LOWER(file_path) LIKE '%.gitlab-ci.yml' AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')
```

## project-creation-traffic
<!-- Suspicious project management traffic -->
Identify HTTP requests indicating a new project or IDE session, preceding the planting of the exploit.

```sqlite target=web role=enrichment params=(scope_hosts=scope_hosts, lookback_days=lookback_days)
~~~yaml
expected: Rows show IPs initiating project setup. High volumes from external or unknown
  IPs warrant closer inspection.
reads:
- device_hostname
- src_endpoint_ip
- time
- url_path
silence: not_evidence_of_absence
source: hb_http_activity
verified: dry-run
verified_at: '2026-10-14'
~~~
SELECT device_hostname, src_endpoint_ip, url_path, time FROM hb_http_activity WHERE (LOWER(url_path) LIKE '%/projects/new%' OR LOWER(url_path) LIKE '%/-/ide/%') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')
```

## early-delivery-read
<!-- Assess delivery risk -->
```agent target=hunter
cite: required
context:
- vulnerable-gitlab-instances
- config-file-changes
- project-creation-traffic
max_iterations: 3
objective: Identify hosts showing a vulnerable version branch combined with recent
  .gitlab-ci.yml writes or project creation events.
success_criteria: A per-host risk assessment focused on delivery indicators.
tools:
- endpoint
- web
```

## rce-behavior-prevalence
<!-- Rare processes spawned by GitLab workers -->
Identify successful RCE by stack-counting rare shell and utility processes spawned by the Ruby application workers.

```sqlite target=endpoint role=detection-candidate params=(git_user=git_user, scope_hosts=scope_hosts, lookback_days=lookback_days)
~~~yaml
baseline:
  compare: first_seen
  window: '{{lookback_days}}d'
expected: A rare shell command or network utility spawned by a GitLab worker is a
  high-fidelity indicator of successful RCE. Silence proves the absence of this specific
  behavior on the GitLab hosts during the window.
prevalence:
  by: device_hostname
  key:
  - cmd
  rare_below: 3
reads:
- device_hostname
- parent_process_name
- process_cmd_line
- process_name
- time
- user_name
silence: evidence_of_absence
source: hb_process_activity
verified: dry-run
verified_at: '2026-10-14'
~~~
SELECT LOWER(process_cmd_line) AS cmd, GROUP_CONCAT(DISTINCT device_hostname) AS hosts, COUNT(DISTINCT device_hostname) AS host_count, MIN(time) AS first_seen FROM hb_process_activity WHERE LOWER(user_name) = LOWER('{{git_user}}') AND (LOWER(parent_process_name) LIKE '%ruby%' OR LOWER(parent_process_name) LIKE '%sidekiq%' OR LOWER(parent_process_name) LIKE '%bundle%') AND (LOWER(process_name) LIKE '%/sh' OR LOWER(process_name) = 'sh' OR LOWER(process_name) LIKE '%/bash' OR LOWER(process_name) = 'bash' OR LOWER(process_name) LIKE '%/python%' OR LOWER(process_name) = 'python' OR LOWER(process_name) LIKE '%/curl' OR LOWER(process_name) = 'curl' OR LOWER(process_name) LIKE '%/wget' OR LOWER(process_name) = 'wget') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days') GROUP BY cmd HAVING host_count <= 3
```

## final-exploit-chain-read
<!-- Review the complete exploit chain -->
```agent target=hunter
cite: required
context:
- early-delivery-read
- rce-behavior-prevalence
max_iterations: 4
objective: 'Determine if any host shows the complete chain: vulnerable software, delivery
  indicators, and rare RCE-like process behavior.'
success_criteria: A per-host verdict of malicious, suspicious, or benign based on
  the strength of the chain.
tools:
- endpoint
- web
```

## route-on-verdict
<!-- Route on verdict -->
if~: "the final-exploit-chain-read verdict is malicious for at least one host" (confidence: high, judge=hunter)
then: → isolate-and-remediate
indeterminate: → analyst-forensic-review
unavailable: → analyst-forensic-review (blind_spot: agent-visibility-gap)
else: → analyst-forensic-review

## isolate-and-remediate
<!-- Isolate and remediate -->
```action target=endpoint
~~~yaml
approval: required
~~~
Isolate the GitLab server from the network. Stop the GitLab service. Collect a forensic image. Upgrade GitLab to a fixed version (19.4.2+) before returning it to service.
```
→ analyst-forensic-review

## analyst-forensic-review
<!-- Analyst forensic review -->
```manual target=analyst
Review the process activity on the GitLab server. Inspect /var/log/gitlab/gitlab-rails/production.log and sidekiq.log for the project path and the UTF-8 characters that triggered the overflow. Identify the source IP of the unauthenticated project creation.
```
→ close-out-report

## close-out-report
<!-- Close out report -->
```manual target=analyst
Record the list of vulnerable vs patched hosts. Log the malicious projects identified. Update the vulnerability management records for CVE-2026-97229.
```
→ end

Run it

Take this hunt into your environment.

Open it in Huntbase to run every step against your own connections, with Scout weighing the evidence and your analysts in command. Or take the open hunt.md file anywhere that reads the format.

Machine-drafted by huntbase-hunt-generation using hb_google/gemini-3-flash-preview, gated by dry-run, lint, then reviewed by a person.