If you run a self-managed GitLab instance that is reachable from the internet, you should treat this as an emergency patch event. GitLab has shipped fixes for a maximum-severity vulnerability — CVE-2026-85706 (CVSS 10.0) — a path traversal flaw in the repository commits API that allows an unauthenticated attacker to read arbitrary files from the underlying server. Within hours of public disclosure, security researchers observed in-the-wild probing against exposed instances.
This is the worst possible combination of attributes for a defensive team: no authentication required, a trivially scriptable HTTP request as the exploitation vehicle, a target platform that by design stores source code, CI/CD secrets, signing keys, and deploy tokens, and a threat actor community that began scanning before many change-advisory boards had even convened.
A CVSS 10.0 unauthenticated file read on a GitLab server is not just a confidentiality bug. Read access to /etc/gitlab/gitlab.rb, Rails secret files, database credentials, Gitaly configuration, or stored SSH keys converts a file-read primitive into full instance compromise — and from there, into a software supply-chain foothold against every project and pipeline that server touches. We have seen this movie before with CI/CD platforms; the difference between an incident and a headline is how fast you patch and how well you can prove you were not hit first.
Technical Analysis
Vulnerability Overview
| Attribute | Detail |
|---|---|
| CVE | CVE-2026-85706 |
| CVSS | 10.0 (Critical) |
| Weakness | Path Traversal (CWE-22) in the repository commits API |
| Authentication | None required |
| Impact | Arbitrary file read from the GitLab server's filesystem |
| Affected component | GitLab REST API — repository commits endpoint |
| Deployment scope | GitLab self-managed (CE/EE); gitlab.com is patched by GitLab |
| Exploitation status | In-the-wild probing observed within hours of disclosure |
How the Attack Works
The repository commits API endpoint (GET /api/v4/projects/:id/repository/commits) accepts parameters that are ultimately used to construct filesystem paths when GitLab resolves repository data through Gitaly. The vulnerable code path fails to properly canonicalize or sandbox attacker-controlled path components. By supplying traversal sequences — ../, URL-encoded variants (%2e%2e%2f, %252e%252e%252f for double-encoding), or mixed-encoding forms — an unauthenticated remote requester can escape the intended repository directory and coax the server into returning the contents of arbitrary files readable by the git service account.
Key exploitation characteristics from a defender's perspective:
- Single GET request. No session, token, CSRF dance, or multi-stage setup. One malformed API call returns file contents in the response body.
- High-value target files. Attackers typically reach for
/etc/gitlab/gitlab.rb(can contain secrets depending on configuration),/etc/gitlab/gitlab-secrets.json(Rails secret_key_base, OTP and DB encryption keys — effectively the keys to the kingdom),/var/opt/gitlab/gitlab-rails/etc/database.yml, user SSH authorized_keys, and CI/CD variable stores. - The
gitlab-secrets.jsonscenario is the nightmare path. Withsecret_key_baseand related encryption keys, an attacker can forge sessions and decrypt database-encrypted secrets. This pivots the bug from "file read" to "unauthenticated admin." - Noisy at the edge, quiet on the host. Exploitation is pure HTTP traffic to the nginx/Puma front end. There is no process execution on the server until a post-exploitation stage, which means your detection lives in web/API logs — not EDR process telemetry.
Exploitation Status
Per reporting, probes began within hours of the advisory going public. That timeline is consistent with what we observe for CVSS 10 pre-auth bugs on developer infrastructure: scanners (both researcher-operated and criminal) fingerprint exposed GitLab instances by version banner and fire traversal payloads at the commits API. Assume that any internet-exposed, unpatched instance has already been enumerated. Even if your instance requires login for projects, pre-auth API paths are reachable before authentication — do not assume an "internal users only" stance protects an internet-facing instance.
Detection & Response
Because exploitation is an HTTP-layer event, your primary telemetry sources are: GitLab nginx access logs (/var/log/gitlab/nginx/gitlab_access.log), GitLab Rails API logs (/var/log/gitlab/gitlab-rails/api_json.log), upstream WAF/reverse-proxy logs, and any CEF/Syslog forwarding you have into Sentinel or your SIEM. The detections below target the observable signature of this attack: traversal sequences — raw or encoded — in request URIs against the repository commits API.
---
title: GitLab CVE-2026-85706 Path Traversal Probe Against Repository Commits API
id: 4f8c2e91-7b3a-4d5e-9a21-6c0d8e5f2b47
status: experimental
description: Detects path traversal sequences (raw or URL-encoded) in requests to the GitLab repository commits API, consistent with CVE-2026-85706 arbitrary file read exploitation attempts.
references:
- https://thehackernews.com/2026/09/gitlab-cvss-10-file-read-flaw-draws-in.html
- https://attack.mitre.org/techniques/T1190/
author: Security Arsenal
date: 2026/09/18
tags:
- attack.initial_access
- attack.t1190
logsource:
category: webserver
service: nginx
detection:
selection_api:
cs-uri|contains: '/repository/commits'
selection_traversal:
cs-uri|contains:
- '../'
- '..\\'
- '%2e%2e'
- '%252e%252e'
- '..%2f'
- '%2e%2e/'
- '..%5c'
condition: selection_api and selection_traversal
falsepositives:
- Extremely rare; legitimate commits API calls do not contain traversal sequences
level: critical
---
title: GitLab Unauthenticated API Reconnaissance and Traversal Scanning
id: 8d1e6a34-2c9f-4b7d-a3e5-9f0c1b2d4e68
status: experimental
description: Detects HTTP 4xx/2xx request bursts containing encoded dot sequences against GitLab API v4 endpoints from a single source, indicative of automated exploitation scanning following CVE-2026-85706 disclosure.
references:
- https://thehackernews.com/2026/09/gitlab-cvss-10-file-read-flaw-draws-in.html
- https://attack.mitre.org/techniques/T1190/
author: Security Arsenal
date: 2026/09/18
tags:
- attack.reconnaissance
- attack.t1595.002
- attack.initial_access
logsource:
category: webserver
service: nginx
detection:
selection_api:
cs-uri|startswith: '/api/v4/'
selection_encoded:
cs-uri|contains:
- '%2e%2e'
- '%252e'
- '%2f..'
- 'etc/gitlab'
- 'gitlab-secrets'
- 'etc/passwd'
condition: selection_api and selection_encoded
falsepositives:
- Misconfigured developer tooling; validate source IP against known scanner ranges
level: high
// Hunt for CVE-2026-85706 exploitation attempts against GitLab instances
// Assumes GitLab nginx access logs forwarded via CEF/Syslog to Sentinel
let traversal = dynamic(["../", "%2e%2e", "%252e%252e", "..%2f", "%2e%2e/", "..%5c", "gitlab-secrets", "etc/passwd", "etc/gitlab"]);
let suspiciousRequests =
CommonSecurityLog
| where TimeGenerated > ago(7d)
| where RequestURL has "/repository/commits" or RequestURL has "/api/v4/"
| extend RequestURL_decoded = url_decode(RequestURL)
| where RequestURL has_any (traversal) or RequestURL_decoded has_any (traversal)
| project TimeGenerated, SourceIP, RequestURL, RequestURL_decoded, RequestMethod, ApplicationProtocol, DeviceProduct, Computer;
let syslogRequests =
Syslog
| where TimeGenerated > ago(7d)
| where SyslogMessage has "repository/commits"
| extend msg_decoded = url_decode(SyslogMessage)
| where SyslogMessage has_any (traversal) or msg_decoded has_any (traversal)
| project TimeGenerated, HostIP, Computer, SyslogMessage;
union suspiciousRequests, syslogRequests
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Attempts=count(), DistinctURIs=dcount(RequestURL)
by SourceIP, Computer
| order by Attempts desc;
// Follow-up: identify sources that also requested /users/sign_in or version banners (fingerprinting behavior)
CommonSecurityLog
| where TimeGenerated > ago(7d)
| where RequestURL has_any ("/api/v4/version", "/users/sign_in", "/explore")
| summarize FingerprintEvents=count() by SourceIP
| join kind=inner (
suspiciousRequests | summarize ExploitAttempts=count() by SourceIP
) on SourceIP
| project SourceIP, FingerprintEvents, ExploitAttempts
| order by ExploitAttempts desc;
-- Artifact: Linux.Forensics.GitLabTraversalHunt
-- Hunts GitLab nginx access and Rails API logs for CVE-2026-85706 traversal attempts
-- and checks whether high-value secret files may have been exposed.
-- 1. Parse nginx access logs for traversal sequences in commits API requests
LET access_log_hits = SELECT
parse_string_with_regex(string=Line,
regex='^(?P<SrcIP>[0-9\.]+).*?"(?P<Method>[A-Z]+) (?P<URI>[^ ]+).*?" (?P<Status>[0-9]{3})') AS Parsed,
Line
FROM parse_lines(filename='/var/log/gitlab/nginx/gitlab_access.log')
WHERE Line =~ 'repository/commits'
AND (Line =~ '\.\./' OR Line =~ '(?i)%2e%2e' OR Line =~ '(?i)%252e'
OR Line =~ '(?i)gitlab-secrets' OR Line =~ '(?i)etc/passwd')
SELECT Parsed.SrcIP AS SourceIP, Parsed.Method AS Method,
Parsed.URI AS RequestURI, Parsed.Status AS HTTPStatus
FROM access_log_hits
-- 2. Check gitlab-rails API log for matching unauthenticated requests
SELECT Line AS ApiLogEntry
FROM parse_lines(filename='/var/log/gitlab/gitlab-rails/api_json.log')
WHERE Line =~ 'repository/commits'
AND (Line =~ '\.\.' OR Line =~ '(?i)%2e')
-- 3. Confirm secrets file presence and last access metadata for scoping
SELECT FullPath, Size, Mtime, Atime
FROM glob(globs=[
'/etc/gitlab/gitlab-secrets.json',
'/etc/gitlab/gitlab.rb',
'/var/opt/gitlab/gitlab-rails/etc/database.yml'
])
#!/bin/bash
# CVE-2026-85706 GitLab Emergency Triage & Remediation Script
# Run as root on the GitLab server. Test in staging before production use.
set -euo pipefail
echo "=== [1/5] Current GitLab version ==="
gitlab-rake gitlab:env:info 2>/dev/null | head -20 || cat /opt/gitlab/version-manifest.txt | head -5
echo "=== [2/5] Checking installed package version ==="
if command -v dpkg &>/dev/null; then
dpkg -l | grep -E 'gitlab-(ce|ee)'
elif command -v rpm &>/dev/null; then
rpm -qa | grep -E 'gitlab-(ce|ee)'
fi
echo "=== [3/5] Hunting logs for traversal exploitation attempts (CVE-2026-85706) ==="
for log in /var/log/gitlab/nginx/gitlab_access.log /var/log/gitlab/nginx/gitlab_access.log.1; do
[ -f "$log" ] || continue
echo "--- $log ---"
grep -iE 'repository/commits' "$log" \
| grep -iE '(\.\./|%2e%2e|%252e|gitlab-secrets|etc/passwd|etc/gitlab)' \
| awk '{print $1, $7, $9}' | sort | uniq -c | sort -rn | head -25 || echo "No hits in $log"
done
echo "=== [3b] Scanning archived/rotated logs ==="
zgrep -hiE 'repository/commits' /var/log/gitlab/nginx/gitlab_access.log*.gz 2>/dev/null \
| grep -iE '(\.\./|%2e%2e|%252e)' | head -25 || echo "No hits in rotated logs"
echo "=== [4/5] Applying the GitLab security patch ==="
echo "Backing up configuration first..."
gitlab-ctl backup-etc 2>/dev/null || cp -a /etc/gitlab /root/gitlab-etc-backup-$(date +%Y%m%d)
if command -v apt-get &>/dev/null; then
apt-get update
apt-get install -y gitlab-ee # use gitlab-ce if running Community Edition
elif command -v yum &>/dev/null; then
yum makecache
yum install -y gitlab-ee # use gitlab-ce if running Community Edition
fi
echo "=== [5/5] Post-patch verification ==="
gitlab-rake gitlab:check SANITIZE=true 2>/dev/null | tail -20
curl -sk "https://127.0.0.1/api/v4/version" | head -c 200; echo
echo ""
echo "ACTION REQUIRED: If step 3 returned hits, treat as a confirmed incident:"
echo " - Rotate ALL secrets in /etc/gitlab/gitlab-secrets.json (gitlab-ctl reconfigure)"
echo " - Reset database credentials, tokens, deploy keys, and CI/CD variables"
echo " - Review /var/log/gitlab/gitlab-rails/audit_json.log for unauthorized access"
Remediation
1. Patch immediately — this is a same-day event. GitLab has released patched versions for CVE-2026-85706 and the other flaws addressed in the same release cycle. Upgrade all self-managed GitLab CE/EE instances to the latest security release published at GitLab Security Releases and confirm against the official advisory for CVE-2026-85706 in the GitLab security advisories portal. Verify your running version with gitlab-rake gitlab:env:info and compare against the fixed versions listed in the advisory. Do not wait for a maintenance window — exploitation is already occurring.
2. If you cannot patch within hours, remove the attack surface. Take the instance off the internet or place it behind an authenticating reverse proxy/VPN. As a temporary mitigation, block requests matching traversal patterns at your WAF or reverse proxy (reject URIs containing ../, %2e%2e, %252e, ..%2f in any /api/v4/ path). This is a compensating control, not a fix — WAF rules can be bypassed with novel encodings, and the patch is the only complete remediation.
3. Assume compromise for internet-exposed unpatched instances and hunt before you celebrate. The probing window opened within hours of disclosure. Grep historical nginx access logs (including rotated .gz archives) for the patterns in the script above. Any 200-response traversal hit against the commits API means a file was read — determine which file from the URI and scope accordingly.
4. Rotate secrets if there is any evidence of successful exploitation. Specifically: regenerate the contents of /etc/gitlab/gitlab-secrets.json (followed by gitlab-ctl reconfigure), rotate the database password, reset all personal access tokens, deploy tokens, runner registration tokens, CI/CD variables containing credentials, and any SSH keys stored on the server. Audit audit_json.log for token creation, project exports, or membership changes you cannot explain. If secret_key_base was exposed, every session and encrypted secret in the instance must be considered compromised.
5. Harden going forward. Enforce IP allowlisting or SSO-fronted access for the API where feasible, ensure nginx access and Rails API logs are forwarded to your SIEM with at least 90-day retention, and enable GitLab's audit event streaming. Add version-banner monitoring: GitLab exposes /api/v4/version unauthenticated by default on many configurations — restricting it reduces your visibility to opportunistic scanners. Finally, track this class of bug in your vulnerability management program with a severity-based SLA: CVSS 10.0 pre-auth flaws on internet-facing developer infrastructure get patched in 24 hours, full stop.
Executive Takeaway
Developer infrastructure is production infrastructure. An unauthenticated 10.0 file-read on your source-of-truth for code, secrets, and pipelines is a supply-chain risk event, not a routine patch. Patch today, hunt your logs going back to before the disclosure date, and rotate secrets if you find hits.
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.