{
  "hunt": {
    "meta": {
      "tlp": "clear",
      "hunt": {
        "handoff": "promote-to-detection",
        "trigger": "intel-report",
        "methodology": "model-assisted",
        "applicability": "campaign-specific",
        "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."
      },
      "name": "GitLab CI/CD Regex Compiler RCE Detection",
      "type": "investigation",
      "labels": [
        "hunt",
        "attack.t1190",
        "attack.t1059",
        "execution",
        "initial access"
      ],
      "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"
        }
      ],
      "targets": {
        "web": {
          "name": "Web server / proxy logs",
          "category": "siem",
          "telemetry": [
            "network"
          ]
        },
        "hunter": {
          "name": "Hunt agent",
          "agent": true
        },
        "analyst": {
          "name": "Tier-2 analyst",
          "role": "analyst"
        },
        "endpoint": {
          "name": "Endpoint telemetry (hb_ surfaces)",
          "category": "endpoint",
          "telemetry": [
            "endpoint"
          ]
        }
      },
      "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.",
      "coverage": [
        {
          "stage": "vulnerable-software-identification",
          "steps": [
            "vulnerable-gitlab-instances"
          ],
          "status": "covered"
        },
        {
          "stage": "malicious-config-delivery",
          "steps": [
            "config-file-changes",
            "project-creation-traffic"
          ],
          "status": "covered"
        },
        {
          "stage": "regex-compiler-exploitation",
          "steps": [
            "rce-behavior-prevalence"
          ],
          "status": "covered"
        },
        {
          "stage": "rce-command-execution",
          "steps": [
            "rce-behavior-prevalence"
          ],
          "status": "covered"
        }
      ],
      "scenario": {
        "stages": [
          {
            "name": "Vulnerable GitLab Instance Detection",
            "slug": "vulnerable-software-identification",
            "tactic": "initial-access",
            "techniques": [
              "T1190"
            ],
            "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"
            ]
          },
          {
            "name": "Malicious CI/CD Configuration Delivery",
            "slug": "malicious-config-delivery",
            "tactic": "initial-access",
            "techniques": [
              "T1190"
            ],
            "observables": [
              ".gitlab-ci.yml",
              "repository path containing UTF-8 characters",
              "unauthenticated creation of public projects"
            ]
          },
          {
            "name": "Regex Engine Control-Flow Hijack",
            "slug": "regex-compiler-exploitation",
            "tactic": "execution",
            "techniques": [
              "T1190",
              "T1059"
            ],
            "observables": [
              "Ruby Onigmo VM operations",
              "GitLab application worker processes"
            ]
          },
          {
            "name": "Command Execution as git User",
            "slug": "rce-command-execution",
            "tactic": "execution",
            "techniques": [
              "T1059"
            ],
            "observables": [
              "operating system commands executed as the git user",
              "shell processes spawned by Ruby GitLab workers"
            ]
          }
        ],
        "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",
      "rationale": "Focus exclusively on self-managed GitLab Community and Enterprise editions. GitLab.com is out of scope.",
      "guardrails": {
        "claims": "no_unsupported",
        "evidence": "citation_required",
        "telemetry": "untrusted",
        "missing_data": "not_benign"
      },
      "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.",
      "parameters": {
        "git_user": {
          "from": {
            "ref": "standard-deployment",
            "kind": "manual",
            "observed": "2026-10-13"
          },
          "type": "string",
          "default": "git",
          "description": "The operating system account that the GitLab service runs as."
        },
        "scope_hosts": {
          "from": {
            "ref": "analyst-input",
            "kind": "manual",
            "observed": "2026-10-13"
          },
          "type": "list[host]",
          "default": [],
          "description": "Limit hunting to these hosts; leave empty to hunt across the entire estate."
        },
        "lookback_days": {
          "from": {
            "ref": "hunt-standard",
            "kind": "manual",
            "observed": "2026-10-13"
          },
          "type": "number",
          "default": "14",
          "description": "Days of history to examine for file and process activity."
        }
      },
      "provenance": {
        "authors": [
          {
            "org": "huntbase.io",
            "name": "Huntbase hunt generation"
          }
        ],
        "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"
        }
      },
      "references": [
        {
          "url": "https://www.rapid7.com/blog/post/etr-cve-2026-97229-critical-unauthenticated-remote-code-execution-in-gitlab",
          "name": "Rapid7 \u2014 CVE-2026-97229: Critical Unauthenticated Remote Code Execution in GitLab"
        }
      ],
      "blind_spots": [
        {
          "id": "agent-visibility-gap",
          "risk": "A host without an agent provides no telemetry for the execution phase, leaving a critical blind spot in the detection chain.",
          "owner": "IT Operations",
          "question": "whether the GitLab instance is enrolled in monitoring",
          "requires": "hb_process_activity from an endpoint agent",
          "remediation": "Ensure all GitLab infrastructure is enrolled in the central EDR/monitoring system."
        },
        {
          "id": "http-body-inspection",
          "risk": "Surface logs show the file was modified but not the content, requiring the agent to infer maliciousness from the subsequent RCE behavior.",
          "owner": "DevSecOps",
          "stage": "malicious-config-delivery",
          "question": "whether the .gitlab-ci.yml specifically contained the overflow regex",
          "requires": "Application layer logging of .gitlab-ci.yml content",
          "remediation": "Enable repository audit logging within GitLab for configuration changes."
        }
      ]
    },
    "name": "GitLab CI/CD Regex Compiler RCE Detection",
    "description": "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."
  },
  "nodes": [
    {
      "id": "hypothesis",
      "type": "hypothesis",
      "label": "Hypothesis",
      "config": {
        "tags": [],
        "coverage": [
          {
            "stage": "vulnerable-software-identification",
            "steps": [
              "vulnerable-gitlab-instances"
            ],
            "status": "covered"
          },
          {
            "stage": "malicious-config-delivery",
            "steps": [
              "config-file-changes",
              "project-creation-traffic"
            ],
            "status": "covered"
          },
          {
            "stage": "regex-compiler-exploitation",
            "steps": [
              "rce-behavior-prevalence"
            ],
            "status": "covered"
          },
          {
            "stage": "rce-command-execution",
            "steps": [
              "rce-behavior-prevalence"
            ],
            "status": "covered"
          }
        ],
        "rationale": "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.",
        "blind_spots": [
          {
            "id": "agent-visibility-gap",
            "risk": "A host without an agent provides no telemetry for the execution phase, leaving a critical blind spot in the detection chain.",
            "owner": "IT Operations",
            "question": "whether the GitLab instance is enrolled in monitoring",
            "requires": "hb_process_activity from an endpoint agent",
            "remediation": "Ensure all GitLab infrastructure is enrolled in the central EDR/monitoring system."
          },
          {
            "id": "http-body-inspection",
            "risk": "Surface logs show the file was modified but not the content, requiring the agent to infer maliciousness from the subsequent RCE behavior.",
            "owner": "DevSecOps",
            "stage": "malicious-config-delivery",
            "question": "whether the .gitlab-ci.yml specifically contained the overflow regex",
            "requires": "Application layer logging of .gitlab-ci.yml content",
            "remediation": "Enable repository audit logging within GitLab for configuration changes."
          }
        ],
        "scoping_notes": "Focus exclusively on self-managed GitLab Community and Enterprise editions. GitLab.com is out of scope.",
        "beyond_detection": "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."
      }
    },
    {
      "id": "vulnerable-gitlab-instances",
      "type": "query",
      "label": "Identify vulnerable GitLab instances",
      "config": {
        "dsl": "sqlite",
        "role": "scoping",
        "source": "endpoint",
        "content": "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.%')",
        "surface": "hb_software_inventory",
        "description": "Narrow the hunt to self-managed GitLab servers running the affected 19.2, 19.3, or 19.4 branches.",
        "expected_signal": "A list of hosts running vulnerable versions. Silence means the estate is patched or does not run self-managed GitLab."
      },
      "parents": [
        {
          "id": "hypothesis"
        }
      ],
      "primitive_config": {
        "dsl": "sqlite",
        "role": "scoping",
        "label": "Identify vulnerable GitLab instances",
        "reads": [
          "device_hostname",
          "package_name",
          "package_version"
        ],
        "source": "hb_software_inventory",
        "target": "endpoint",
        "content": "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.%')",
        "silence": "not_evidence_of_absence",
        "expected": "A list of hosts running vulnerable versions. Silence means the estate is patched or does not run self-managed GitLab.",
        "verified": "dry-run",
        "verified_at": "2026-10-14"
      }
    },
    {
      "id": "config-file-changes",
      "type": "query",
      "label": "Malicious CI/CD configuration writes",
      "config": {
        "dsl": "sqlite",
        "role": "enrichment",
        "source": "endpoint",
        "content": "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')",
        "surface": "hb_file_activity",
        "description": "Find recent writes to the .gitlab-ci.yml file, which is the primary delivery mechanism for the exploit.",
        "expected_signal": "A hit identifies the timestamp and actor behind a configuration change. Normal DevOps activity will create noise here."
      },
      "parents": [
        {
          "id": "vulnerable-gitlab-instances"
        }
      ],
      "primitive_config": {
        "dsl": "sqlite",
        "role": "enrichment",
        "label": "Malicious CI/CD configuration writes",
        "reads": [
          "actor_user_name",
          "device_hostname",
          "file_path",
          "time"
        ],
        "source": "hb_file_activity",
        "target": "endpoint",
        "content": "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')",
        "silence": "not_evidence_of_absence",
        "expected": "A hit identifies the timestamp and actor behind a configuration change. Normal DevOps activity will create noise here.",
        "verified": "dry-run",
        "verified_at": "2026-10-14"
      }
    },
    {
      "id": "project-creation-traffic",
      "type": "query",
      "label": "Suspicious project management traffic",
      "config": {
        "dsl": "sqlite",
        "role": "enrichment",
        "source": "web",
        "content": "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')",
        "surface": "hb_http_activity",
        "description": "Identify HTTP requests indicating a new project or IDE session, preceding the planting of the exploit.",
        "expected_signal": "Rows show IPs initiating project setup. High volumes from external or unknown IPs warrant closer inspection."
      },
      "parents": [
        {
          "id": "vulnerable-gitlab-instances"
        }
      ],
      "primitive_config": {
        "dsl": "sqlite",
        "role": "enrichment",
        "label": "Suspicious project management traffic",
        "reads": [
          "device_hostname",
          "src_endpoint_ip",
          "time",
          "url_path"
        ],
        "source": "hb_http_activity",
        "target": "web",
        "content": "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')",
        "silence": "not_evidence_of_absence",
        "expected": "Rows show IPs initiating project setup. High volumes from external or unknown IPs warrant closer inspection.",
        "verified": "dry-run",
        "verified_at": "2026-10-14"
      }
    },
    {
      "id": "early-delivery-read",
      "type": "analytic",
      "label": "Assess delivery risk",
      "config": {
        "cite": "required",
        "tools": [
          "endpoint",
          "web"
        ],
        "context": [
          "vulnerable-gitlab-instances",
          "config-file-changes",
          "project-creation-traffic"
        ],
        "objective": "Identify hosts showing a vulnerable version branch combined with recent .gitlab-ci.yml writes or project creation events.",
        "description": "Determine if any vulnerable host is currently showing signs of exploit delivery.",
        "max_iterations": 3,
        "expected_signal": "A summary of vulnerable hosts with associated delivery indicators.",
        "success_criteria": "A per-host risk assessment focused on delivery indicators."
      },
      "parents": [
        {
          "id": "config-file-changes",
          "kind": "merge"
        },
        {
          "id": "project-creation-traffic",
          "kind": "merge"
        }
      ]
    },
    {
      "id": "rce-behavior-prevalence",
      "type": "query",
      "label": "Rare processes spawned by GitLab workers",
      "config": {
        "dsl": "sqlite",
        "role": "detection-candidate",
        "source": "endpoint",
        "content": "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",
        "surface": "hb_process_activity",
        "description": "Identify successful RCE by stack-counting rare shell and utility processes spawned by the Ruby application workers.",
        "expected_signal": "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."
      },
      "parents": [
        {
          "id": "early-delivery-read"
        }
      ],
      "primitive_config": {
        "dsl": "sqlite",
        "role": "detection-candidate",
        "label": "Rare processes spawned by GitLab workers",
        "reads": [
          "device_hostname",
          "parent_process_name",
          "process_cmd_line",
          "process_name",
          "time",
          "user_name"
        ],
        "source": "hb_process_activity",
        "target": "endpoint",
        "content": "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",
        "silence": "evidence_of_absence",
        "baseline": {
          "window": "{{lookback_days}}d",
          "compare": "first_seen"
        },
        "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.",
        "verified": "dry-run",
        "prevalence": {
          "by": "device_hostname",
          "key": [
            "cmd"
          ],
          "rare_below": 3
        },
        "verified_at": "2026-10-14"
      }
    },
    {
      "id": "final-exploit-chain-read",
      "type": "analytic",
      "label": "Review the complete exploit chain",
      "config": {
        "cite": "required",
        "tools": [
          "endpoint",
          "web"
        ],
        "context": [
          "early-delivery-read",
          "rce-behavior-prevalence"
        ],
        "objective": "Determine if any host shows the complete chain: vulnerable software, delivery indicators, and rare RCE-like process behavior.",
        "description": "Correlate the delivery indicators with the execution indicators to confirm exploitation.",
        "max_iterations": 4,
        "expected_signal": "A final per-host verdict.",
        "success_criteria": "A per-host verdict of malicious, suspicious, or benign based on the strength of the chain."
      },
      "parents": [
        {
          "id": "rce-behavior-prevalence"
        }
      ]
    },
    {
      "id": "route-on-verdict",
      "type": "checkpoint",
      "label": "Route on verdict",
      "config": {
        "fuzzy": true,
        "judge": "hunter",
        "question": "the final-exploit-chain-read verdict is malicious for at least one host",
        "condition": "the final-exploit-chain-read verdict is malicious for at least one host",
        "blind_spot": "agent-visibility-gap",
        "confidence": "high",
        "description": "Direct the hunt to immediate isolation or manual review based on the agent's confidence.",
        "checkpoint_type": "mandatory"
      },
      "parents": [
        {
          "id": "final-exploit-chain-read"
        }
      ]
    },
    {
      "id": "isolate-and-remediate",
      "type": "action",
      "label": "Isolate and remediate",
      "config": {
        "target": "endpoint",
        "description": "Halt the attack and bring the server to a secure, patched state.",
        "instructions": "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.",
        "action_approval": "required"
      },
      "parents": [
        {
          "id": "route-on-verdict",
          "branch": "on_supports"
        }
      ]
    },
    {
      "id": "analyst-forensic-review",
      "type": "task",
      "label": "Analyst forensic review",
      "config": {
        "assignee": "analyst",
        "description": "Verify the findings and identify the specific malicious project path.",
        "instructions": "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."
      },
      "parents": [
        {
          "id": "route-on-verdict",
          "branch": "default"
        },
        {
          "id": "route-on-verdict",
          "branch": "on_unavailable"
        },
        {
          "id": "route-on-verdict",
          "branch": "on_refutes"
        },
        {
          "id": "isolate-and-remediate"
        }
      ]
    },
    {
      "id": "close-out-report",
      "type": "task",
      "label": "Close out report",
      "config": {
        "assignee": "analyst",
        "description": "Document the hunt results and any remediation taken.",
        "instructions": "Record the list of vulnerable vs patched hosts. Log the malicious projects identified. Update the vulnerability management records for CVE-2026-97229."
      },
      "parents": [
        {
          "id": "analyst-forensic-review"
        }
      ]
    }
  ]
}