Back to Intelligence

GitLab RCE Attack on Alpine Linux Infrastructure: Detection Hardening and Response Guide

SA
Security Arsenal Team
November 1, 2026
11 min read

Between October 20 and 21, attackers attempted to exploit a critical remote code execution vulnerability in GitLab against Alpine Linux's development infrastructure. The Alpine Linux project responded with a decision every incident responder should study: they did not simply patch and move on. They rebuilt the GitLab server from scratch and reset all user passwords over concerns of a possible compromise.

That response posture is the story here. Alpine Linux is not a hobby project — it is the base image for an enormous share of the world's containers, CI/CD pipelines, and embedded systems. A successful compromise of its development infrastructure would be a supply-chain event with blast radius measured in millions of downstream deployments. When the maintainers of a project like that treat an attempted exploit as a probable compromise and act accordingly, defenders running their own self-hosted GitLab instances should take the hint.

If you operate a self-hosted GitLab instance — Omnibus, source, or container-based — you are running one of the most-attacked pieces of infrastructure software in existence. GitLab holds source code, CI variables, signing keys, deploy tokens, and pipeline runners that execute arbitrary jobs. A single RCE on GitLab is rarely contained to GitLab. This post breaks down what defenders should learn from the Alpine incident: how these attacks present, what to hunt for, and how to harden and verify your own deployment.

Technical Analysis

Affected platform

  • Product: GitLab self-managed instances (Community Edition and Enterprise Edition), typically deployed via Omnibus packages, Docker images, or Helm charts on Kubernetes.
  • Target in this incident: Alpine Linux's development infrastructure — the GitLab server used to coordinate development of the Alpine distribution.
  • Attack window: October 20–21, with exploitation attempted but compromise unconfirmed. The project chose to assume compromise pending verification.

Why GitLab RCEs are disproportionately dangerous

GitLab's attack surface is unusually rich for attackers:

  1. The Rails application layer (Puma/Unicorn workers) handles file uploads, repository rendering, markdown parsing, and API calls — historically the source of path traversal and file-write primitives that escalate to RCE.
  2. Sidekiq background workers process webhooks, CI artifacts, and imports — a second execution path that often runs with broad filesystem and network access.
  3. Gitaly brokers all git operations via gRPC, meaning repository content itself can be an attack vector.
  4. CI/CD runners connected to the instance execute pipeline jobs — an attacker with code execution on the server can often pivot into runner infrastructure and harvest cloud credentials, SSH keys, and registry tokens stored as CI variables.

In a distribution like Alpine's case, the stakes compound: the GitLab server is adjacent to package signing, release automation, and the build pipeline that produces artifacts consumed globally. This is exactly the profile of target that supply-chain motivated actors prioritize.

Exploitation status

The Alpine incident involved an attempted exploitation observed in the wild against production infrastructure — not a theoretical exercise. The project publicly disclosed the attempt, rebuilt the server as a containment measure, and forced a global password reset. Whether or not the attempt succeeded is the key forensic question, and Alpine's posture — assume breach, rebuild, rotate — is the correct answer when that question cannot be answered with confidence.

No specific CVE identifier has been disclosed in the public reporting tied to this incident, and we will not speculate on one. The defensive guidance below applies to the class of GitLab RCE attacks: web-tier exploitation against a self-managed instance followed by post-exploitation through the Rails/Sidekiq process tree.

Detection & Response

The detections below target the behaviors that GitLab RCE exploitation actually produces: suspicious HTTP requests against the application tier, shells and command execution spawned by GitLab worker processes, and artifacts dropped into GitLab data directories.

Sigma Rules

YAML
---
title: GitLab Worker Process Spawning Shell or Command Interpreter
id: 3f8c2a71-9d4e-4b6a-a512-7e1f4c9d8b02
status: experimental
description: Detects GitLab Puma, Sidekiq, or Gitaly worker processes spawning shells, interpreters, or download tools — a strong indicator of post-exploitation following a web-tier RCE against a self-managed GitLab instance.
references:
  - https://www.bleepingcomputer.com/news/security/alpine-linux-rebuilds-gitlab-after-attempted-rce-attack/
  - https://attack.mitre.org/techniques/T1190/
  - https://attack.mitre.org/techniques/T1059/
author: Security Arsenal
date: 2026/10/23
tags:
  - attack.initial_access
  - attack.t1190
  - attack.execution
  - attack.t1059.004
logsource:
  category: process_creation
  product: linux
detection:
  selection_parent:
    ParentImage|contains:
      - '/puma'
      - '/sidekiq'
      - '/gitaly'
      - '/gitlab-workhorse'
      - '/unicorn'
  selection_child:
    Image|endswith:
      - '/sh'
      - '/bash'
      - '/dash'
      - '/python'
      - '/python3'
      - '/perl'
      - '/ruby'
      - '/curl'
      - '/wget'
      - '/nc'
      - '/ncat'
      - '/socat'
  condition: selection_parent and selection_child
falsepositives:
  - GitLab backup, upgrade, or maintenance rake tasks spawning shells under an operator session
  - Custom CI housekeeping hooks running directly on the server host
level: high
---
title: Suspicious HTTP Request Patterns Against GitLab Application Endpoints
id: 8b1d4e62-2a7f-4c39-b845-3d9a1e5f7c10
status: experimental
description: Detects inbound HTTP requests to GitLab API and upload endpoints carrying encoded payloads, traversal sequences, or command-injection metacharacters consistent with RCE exploit attempts.
references:
  - https://www.bleepingcomputer.com/news/security/alpine-linux-rebuilds-gitlab-after-attempted-rce-attack/
  - https://attack.mitre.org/techniques/T1190/
author: Security Arsenal
date: 2026/10/23
tags:
  - attack.initial_access
  - attack.t1190
logsource:
  category: webserver
detection:
  selection_uri:
    cs-uri|contains:
      - '/api/v4/'
      - '/uploads/'
      - '/import/'
      - '/groups/'
      - '/projects/'
  selection_payload:
    cs-uri|contains:
      - '%2e%2e'
      - '../'
      - '%3b'
      - '%60'
      - '%24%28'
      - '/etc/passwd'
      - 'bash%20'
      - '|sh'
  condition: all of selection_*
falsepositives:
  - Repository paths legitimately containing unusual encoded characters (rare)
  - Vulnerability scanner traffic from authorized internal scanners
level: medium
---
title: File Written to GitLab Uploads or Rails Temp Directory by Service Account
id: 5c2e9a40-1f6b-4d82-9c37-4a8b2d6e1f93
status: experimental
description: Detects executable or script files created in GitLab uploads, tmp, or public directories outside of normal deployment workflows — a common webshell or payload staging location after application-tier compromise.
references:
  - https://www.bleepingcomputer.com/news/security/alpine-linux-rebuilds-gitlab-after-attempted-rce-attack/
  - https://attack.mitre.org/techniques/T1505.003/
author: Security Arsenal
date: 2026/10/23
tags:
  - attack.persistence
  - attack.t1505.003
  - attack.t1105
logsource:
  category: file_event
  product: linux
detection:
  selection_path:
    TargetFilename|contains:
      - '/var/opt/gitlab/gitlab-rails/uploads/'
      - '/var/opt/gitlab/gitlab-rails/shared/'
      - '/opt/gitlab/embedded/service/gitlab-rails/public/'
      - '/var/opt/gitlab/gitlab-rails/tmp/'
  selection_ext:
    TargetFilename|endswith:
      - '.sh'
      - '.rb'
      - '.py'
      - '.php'
      - '.elf'
      - '.so'
  condition: all of selection_*
falsepositives:
  - GitLab upgrades and reconfigure runs writing embedded Ruby components
  - Legitimate LFS/artifact storage activity (which normally has hashed, extensionless paths)
level: high

KQL — Microsoft Sentinel / Defender Hunting

This query hunts Syslog-ingested Linux hosts for GitLab worker processes spawning shells or tooling, plus authentication anomalies on the GitLab host. It assumes GitLab servers forward authd/auditd telemetry to Sentinel via the Syslog or AMA connector.

KQL — Microsoft Sentinel / Defender
let gitlab_parents = dynamic(["puma", "sidekiq", "gitaly", "gitlab-workhorse", "unicorn"]);
let suspicious_children = dynamic(["/bin/sh", "/bin/bash", "/usr/bin/curl", "/usr/bin/wget", "/usr/bin/python3", "/usr/bin/perl", "nc", "ncat", "socat"]);
union isfuzzy=true
    (Syslog
    | where TimeGenerated > ago(14d)
    | where ProcessName has_any (gitlab_parents)
    | where SyslogMessage has_any (suspicious_children)
    | project TimeGenerated, Computer, ProcessName, SyslogMessage, HostIP
    | extend Detection = "GitLab worker spawned suspicious child"),
    (SecurityEvent
    | where TimeGenerated > ago(14d)
    | where EventID == 4624 and LogonType in (3, 10)
    | where Computer has_any ("gitlab", "git-")
    | summarize LogonCount = count(), SourceIPs = make_set(IpAddress) by Account, Computer, bin(TimeGenerated, 1h)
    | extend Detection = "Interactive/network logon to GitLab host")
| order by TimeGenerated desc

For Defender for Endpoint onboarded GitLab servers, pivot on the process tree directly:

KQL — Microsoft Sentinel / Defender
DeviceProcessEvents
| where TimeGenerated > ago(14d)
| where InitiatingProcessFileName has_any ("puma", "sidekiq", "gitaly", "gitlab-workhorse")
| where FileName in~ ("sh", "bash", "dash", "curl", "wget", "python3", "perl", "ruby", "nc", "ncat", "socat")
| project TimeGenerated, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine, FileName, ProcessCommandLine, AccountName
| order by TimeGenerated desc

Velociraptor VQL Hunt

This artifact enumerates the process tree on a suspected GitLab host and inventories recently created executable/script artifacts in GitLab data paths — the two fastest triage steps before deciding whether a rebuild (like Alpine's) is warranted.

VQL — Velociraptor
-- Triage GitLab host for post-exploitation: worker-spawned processes and staged payloads
SELECT Pid, Ppid, Name, Exe, CommandLine, Username, CreateTime
FROM pslist()
WHERE (Name =~ '(?i)puma|sidekiq|gitaly|workhorse|unicorn'
   AND True)
   OR CommandLine =~ '(?i)curl|wget|/bin/sh|/bin/bash|python|perl|ncat|socat'

-- Inventory recent files in GitLab data directories
SELECT FullPath, Size, Mtime, Ctime, Mode
FROM glob(globs=[
  '/var/opt/gitlab/gitlab-rails/uploads/**/*',
  '/var/opt/gitlab/gitlab-rails/tmp/**/*',
  '/opt/gitlab/embedded/service/gitlab-rails/public/**/*'
])
WHERE Mtime > now() - 604800
  AND (FullPath =~ '(?i)\\.(sh|rb|py|php|elf|so)$' OR Mode =~ 'x')
ORDER BY Mtime DESC

Verification and Hardening Script

Run this on a self-managed GitLab host (Omnibus deployment) to verify version currency, audit for unauthorized access material, and surface indicators that should trigger a rebuild-and-rotate decision like Alpine's.

Bash / Shell
#!/usr/bin/env bash
# GitLab post-attempt triage & hardening — run as root on the GitLab host
set -euo pipefail

echo "=== [1] Installed GitLab version ==="
gitlab-rake gitlab:env:info 2>/dev/null | grep -i version || cat /opt/gitlab/version-manifest.txt | grep gitlab-ee || true

echo "=== [2] Check for pending security updates (Debian/Ubuntu & RHEL) ==="
if command -v apt-get >/dev/null; then
  apt-get update -qq && apt-cache policy gitlab-ee gitlab-ce | head -20
elif command -v dnf >/dev/null; then
  dnf check-update gitlab-ee gitlab-ce || true
fi

echo "=== [3] User accounts with recent password/token anomalies ==="
gitlab-rails runner 'User.where("updated_at > ?", 7.days.ago).each { |u| puts "#{u.username}\t#{u.updated_at}\tadmin=#{u.admin}" }' 2>/dev/null || echo "rails runner unavailable"

echo "=== [4] Personal access tokens & deploy tokens created in last 14 days ==="
gitlab-rails runner 'PersonalAccessToken.where("created_at > ?", 14.days.ago).each { |t| puts "#{t.user&.username}\t#{t.name}\t#{t.created_at}\tscopes=#{t.scopes}" }' 2>/dev/null || true

echo "=== [5] Suspicious child processes of GitLab workers (last 24h via auditd if present) ==="
if command -v ausearch >/dev/null; then
  ausearch -ts recent -i 2>/dev/null | grep -Ei 'puma|sidekiq|gitaly' | grep -Ei 'sh|bash|curl|wget|python|perl' | tail -30 || echo "none"
else
  ps auxf | grep -Ei 'puma|sidekiq|gitaly' | grep -Ei 'sh|bash|curl|wget|python' || echo "none found"
fi

echo "=== [6] Recently modified executable/script files in GitLab data paths ==="
find /var/opt/gitlab/gitlab-rails/uploads /var/opt/gitlab/gitlab-rails/tmp \
  -type f \( -name '*.sh' -o -name '*.rb' -o -name '*.py' -o -name '*.elf' -o -perm -111 \) \
  -mtime -7 -ls 2>/dev/null | tail -40 || true

echo "=== [7] Admin audit events (unauthorized changes, new admins, group exports) ==="
gitlab-rails runner 'AuditEvent.where("created_at > ?", 7.days.ago).order(created_at: :desc).limit(50).each { |a| puts "#{a.created_at}\t#{a.entity_type}##{a.entity_id}\t#{a.details}" }' 2>/dev/null || true

echo "=== [8] Enforce: disable public sign-ups, require 2FA, confirm IP rate limits ==="
grep -Ei "signup_enabled|require_two_factor|rack_attack" /etc/gitlab/gitlab.rb || echo "Review /etc/gitlab/gitlab.rb for signup_enabled=false, 2FA enforcement, and rack_attack throttling"

echo ""
echo "DECISION GUIDANCE: Any unexplained finding in sections 4–7 on an internet-facing instance"
echo "warrants the Alpine playbook: rebuild from known-good, rotate ALL credentials/tokens/keys,"
echo "and invalidate CI/CD variables before restoring service."

Remediation

Whether you are responding to a confirmed attempt or hardening proactively, apply the following in order:

  1. Patch to the latest GitLab release immediately. GitLab ships security releases monthly and out-of-band for criticals. Check your installed version against the current release at the official GitLab releases page (https://about.gitlab.com/releases/categories/releases/) and the GitLab security advisories index. Do not run more than one patch release behind on an internet-facing instance — treat it as a SLA.
  2. If an exploit attempt is confirmed against your instance, assume compromise. Follow Alpine's model: rebuild the host from known-good infrastructure rather than cleaning in place, then rotate everything the instance could reach — all user passwords, personal access tokens, project/group access tokens, deploy tokens, runner registration tokens, CI/CD variables containing secrets, OAuth application secrets, and any SSH keys stored on the host.
  3. Force a global credential reset. Revoke all sessions and PATs (PersonalAccessToken.update_all(revoked: true) via rails console as an emergency measure), enforce 2FA at the instance level, and require password re-entry on next login.
  4. Reduce attack surface. Disable public sign-ups, disable project imports you do not use, place the instance behind a reverse proxy or VPN where feasible, and enable GitLab's built-in rate limiting (Rack Attack) on authentication and API endpoints.
  5. Protect the runner tier. Use separate, ephemeral runners with no network path back to the GitLab host's internal services. Rotate runner registration tokens. Audit which projects hold protected CI variables.
  6. Instrument the host. Forward auditd/execve telemetry and GitLab application logs (/var/log/gitlab/gitlab-rails/production_json.log, audit_json.log, nginx logs) to your SIEM before you need them. The detections above are useless without collection in place.
  7. Validate backups and restore. Alpine could rebuild because their service was reconstructable. Verify yours is: test a full restore of /etc/gitlab (including secrets — back up gitlab-secrets.json), repositories, and the database on a schedule.

Related Resources

Security Arsenal Penetration Testing Services AlertMonitor Platform Book a SOC Assessment vulnerability-management Intel Hub

Is your security operations ready?

Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.