One HTTP request, no account, no user interaction, and a file from the GitLab server comes back in the response body. That is CVE-2026-85706, rated CVSS 10.0 and fixed on September 10 in GitLab 19.3.2, 19.2.6 and 19.1.8. Researchers were watching probes for it a day later. CISA added it to the Known Exploited Vulnerabilities catalog on September 11 and set the federal remediation date three days out, at September 14.
The ticket most teams opened over the weekend says "upgrade GitLab." Upgrading closes the hole. It has no effect on the files that already left the server, and on a default Linux package install those files include the keys GitLab uses to sign sessions and to encrypt every secret in its database.
This one is yours if you run self-managed GitLab Community or Enterprise Edition anywhere in the range 18.7 through 19.3.1, including the 19.2 series below 19.2.6 and the 19.3 series below 19.3.2. GitLab.com is already running the patched version, and GitLab Dedicated customers have nothing to do. If the only GitLab you touch is the SaaS one, close the tab and go spend the hour on something that is yours.
The population that has to act is larger than the one that shows up on an asset inventory. Self-managed GitLab is what a four-person development team stands up on a single VM because the free tier covers what they need. It is what a contractor installed in 2022 and handed over with the root password in a shared document. It is the build server behind the VPN that nobody classified as production because it does not serve customers. All three hold source code, deploy keys and the credentials that reach production, and all three run the same commits API.
A commits API that forgot two things
GitLab's advisory describes the defect in one sentence: an unauthenticated user could have read arbitrary files from the GitLab server "due to improper path confinement and missing authentication enforcement in the repository commits API." Two failures stacked. The endpoint accepted a file path it should have refused, and it served the request without checking who was asking.
watchTowr, which reported behavioural probes against its honeypot network within a day of the release, points defenders at the same place: requests to /api/v4/projects/{id}/repository/commits/ carrying a file.path parameter. That is the detection surface, and it is the reason this bug is dangerous at scale. No chain, no memory corruption, no race. A single request an attacker can fire at every GitLab banner on the internet.
The CVSS 10.0 is earned rather than inflated. Network vector, low complexity, no privileges, no user interaction, and a scope change, because the files the web application hands over belong to the host underneath it. Horizon3's analysis of the same flaw reaches the operational conclusion that GitLab's release notes skip: rotate any credential the GitLab process could read.
Worth knowing before you plan the change window: the same release fixed eighteen CVEs. The second critical is CVE-2026-87719 at 9.9, an insecure deserialization in the GraphQL subscription serializer that an authenticated user with Duo Chat access can use to reach server-side configuration and credentials. Below those sit a buffer overflow in the Unicode conversion wrapper at 8.5, a pipeline execution policy that exposes CI/CD variables at 8.5, and an authorization defect in CI/CD variable scoping at 7.7. The upgrade earns its downtime on its own.
CISA put this in the three-day bucket, and that is the instruction
The September 14 due date is not a round number somebody picked. Under Binding Operational Directive 26-04, CISA sorts vulnerabilities by four variables: whether the asset is publicly exposed, whether the CVE is on the KEV catalog, whether exploitation is automatable, and whether successful exploitation yields partial or total control. The worst cell in that table, where all four go the wrong way, carries a three-day remediation window and a forensic triage requirement.
Read that pairing again, because it is the whole argument of this article stated by the government in a compliance document. The same directive that demands the patch in three days also demands that agencies determine whether the system was compromised before the patch went on. CISA does not treat a file-read bug on an exposed asset as closed when the version number changes.
You are almost certainly not a federal civilian agency. The reasoning still transfers, because it is derived from the mechanics rather than from policy. An automatable, unauthenticated read against an internet-reachable asset has a wide window between public patch and local patch, and everything an attacker collected in that window stays valid afterward.
What the GitLab process can actually read
Precision matters here, because the panic version of this story is wrong in a way that wastes a weekend. The vulnerable code runs inside the Rails application, as the git user. It cannot read files that user cannot read. On a Linux package install, /etc/gitlab/gitlab.rb and /etc/gitlab/gitlab-secrets.json are owned by root at mode 0600, and they are out of reach.
The copies the application needs are not. Those live under /var/opt/gitlab/gitlab-rails/etc/, readable by the account the vulnerable code runs as, and they carry the same material:
secrets.ymlholdssecret_key_base,db_key_base,otp_key_baseand the encrypted-settings key. The first signs and verifies session cookies. The second decrypts the encrypted columns in the database.database.ymlholds the PostgreSQL username and password.gitlab.ymlholds the LDAP bind account, SMTP credentials and object-storage configuration.resque.ymlholds the Redis connection string, password included.
Beyond the configuration directory sit the repositories themselves under /var/opt/gitlab/git-data, job artifacts and CI job logs under /var/opt/gitlab/gitlab-rails/shared, the git user's authorized_keys, and the Rails and NGINX logs, which on a busy instance contain tokens that developers pasted into URLs.
The escalation follows from the first bullet. An attacker holding secret_key_base can forge a signed session cookie and return as an authenticated user without touching the login page. An attacker holding db_key_base plus the PostgreSQL password from database.yml can decrypt the encrypted columns directly: CI/CD variables, runner authentication tokens, integration tokens, two-factor secrets. Those are the credentials that reach your cloud accounts, your container registry and your production deploys. Source code theft is the loud outcome. Standing access to the pipeline is the expensive one.
Check whether you were read, not only whether you are patched
Start with inventory, because the instances that get exploited are the ones nobody listed.
# On each known host: exact version, and whether it is a fixed build.
sudo gitlab-rake gitlab:env:info | grep -i -A2 "GitLab information"
# Fixed builds are 19.3.2, 19.2.6, 19.1.8 or later.
# Anything from 18.7 up to those numbers is in range.
# From a jump box, for every instance you believe you own:
curl -sS -H "PRIVATE-TOKEN: $GITLAB_TOKEN" \
https://gitlab.example.com/api/v4/version | jq .
# Find the ones nobody listed: GitLab sign-in pages on your address space.
nmap -Pn -p 80,443,8080 --open -oG - 203.0.113.0/24 \
| awk '/Ports:/{print $2}'
Then hunt the request shape. The endpoint and parameter are public, the payload is not printed here, and neither is needed: any request to the commits API carrying a file.path parameter from a client that has never authenticated is worth a look.
# NGINX access log. Linux package paths; adjust for source or Docker.
sudo zgrep -h "repository/commits" \
/var/log/gitlab/nginx/gitlab_access.log* \
| grep -i "file\.path" \
| awk '{print $1, $4, $9, $10}' | sort | uniq -c | sort -rn | head -50
# Rails API log records the parameters GitLab actually parsed.
sudo jq -r 'select(.path? // "" | test("repository/commits"))
| [.time, .remote_ip, .status, .path, (.params|tostring)]
| @tsv' /var/log/gitlab/gitlab-rails/api_json.log \
| grep -i "file\.path" | head -50
A 404 or 403 on those lines is a probe that found nothing. A 200 with a response size larger than a commit object, from a source address with no prior authenticated session, is the line that changes your day. Record the source addresses and the timestamps before you rotate anything, because you will want them when you reconstruct the window.
When the logs do not reach back far enough
The Linux package rotates these logs daily and keeps roughly a month. Container and Kubernetes deployments frequently keep far less, and a busy instance behind a load balancer may have the useful record on the balancer rather than the host. If your retention is shorter than the exposure window, you do not have a negative result. You have no result, and the correct reading of no result on an internet-reachable instance is to rotate as though the read succeeded.
Rotate the secrets file before you close the upgrade ticket
Work in blast-radius order, largest first. One: the contents of secrets.yml, which invalidates forged session cookies and the decryption path into the database. Two: the PostgreSQL password, the Redis password, the LDAP bind account and the SMTP credential. Three: every CI/CD variable at project and group scope, starting with the cloud keys and registry credentials. Four: runner authentication tokens, which means re-registering runners. Five: personal, project and group access tokens, deploy tokens and deploy keys. Then confirm the instance still agrees with itself by running sudo gitlab-rake gitlab:doctor:secrets, which walks every encrypted value in the database and reports what the current secrets file can no longer decrypt.
Budget for the disruption rather than discovering it at 2 a.m. GitLab's own backup troubleshooting documentation spells out what happens when the secrets change: CI/CD variables have to be reset, runner tokens have to be reissued, and some encrypted tables may need to be cleared. That is a planned maintenance window on a Tuesday. It is also the only version of this response that ends with credentials an attacker cannot use, and a two-person team can do all five steps in an afternoon if they do them in order.
If you find a 200 in those logs and you do not have an incident response plan that covers a compromised build server, that is the engagement to book. We help small teams build and test the playbook before the log line shows up, and we run the reconstruction when it already has.
Need an incident response plan before the next attack?
We help organizations build and test incident response playbooks, including the credential-rotation runbook for a compromised build server. Book a session with our team.
