/ Security  ·  September 14, 2026  ·  17 min read

GitLab shipped a 10.0 on September 10. On a default install, the file that decrypts your whole instance is world readable.

CVE-2026-85706 is a CVSS 10.0 in GitLab's repository commits API: an unauthenticated request reads arbitrary files off the server. CISA added it to the exploited list the next day and gave federal agencies three days, which expires today. The part nobody is quoting is what a file read is actually worth here. We read the Omnibus packaging source at the 19.3.2 release tag: /etc/gitlab/gitlab-secrets.json is written mode 0600 and root owned, but the copy GitLab hands to Rails at /var/opt/gitlab/gitlab-rails/etc/secrets.yml carries the same eight keys at mode 0644, world readable. The second trap is the version band. GitLab backports security fixes to three minor lines only, so the 53 releases between 18.7 and 19.0.x are affected with no patch to apply, and the way out goes through required upgrade stops. Also: GitLab has already been on CISA's exploited list twice this year for bugs it fixed in 2021, which tells you how large the unpatched population is. What to check, what the rotation actually costs, and the three questions to ask if someone else holds your source code.

By Rushil Shah
SecurityWeb DevelopmentSmall Business

There is a particular kind of vulnerability that reads as mild and is not. “Arbitrary file read” sounds like an information leak, and information leaks sound like something you schedule. Remote code execution gets the pager; file disclosure gets a ticket.

That instinct is usually right and it is wrong here, for a reason that has nothing to do with the bug and everything to do with how GitLab packages itself. We went and read the packaging source to be sure, and the finding in the middle of this post is the one we have not seen anywhere else in the coverage.

Everything below was checked against primary sources on September 14, 2026: GitLab’s own CVE record, its release metadata API, the Omnibus cookbooks at the exact release tag, CISA’s Known Exploited Vulnerabilities feed, and NVD.

What GitLab actually disclosed

On September 10, 2026 at 22:52 UTC, GitLab published three patch releases at once: 19.3.2, 19.2.6 and 19.1.8. We confirmed those timestamps directly from version.gitlab.com, GitLab’s own release metadata service, rather than from the blog post.

The release closed eighteen security issues that we could count on the page. Two are critical.

CVE-2026-85706 is the one that matters. GitLab’s own CVE record, published through its CNA, scores it CVSS 10.0 on the vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N, classified CWE-22, path traversal. The description, verbatim:

GitLab has remediated an issue in GitLab CE/EE affecting all versions from 18.7 before 19.1.8, 19.2 before 19.2.6, and 19.3 before 19.3.2 that, under certain conditions, 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.

Read the vector rather than the number. PR:N means no privileges. UI:N means nobody has to click anything. AV:N means over the network. S:C means the blast radius leaves the vulnerable component, which is the scoring decision that pushes it from 9.x to a perfect 10. And A:N, no availability impact, is the tell that this is a read: it takes nothing down, it just hands things over.

The second critical, CVE-2026-87719 at 9.9, is an insecure deserialization in the GraphQL subscription serializer. It is Enterprise Edition only and needs an authenticated user with Duo Chat access, so it is a smaller population, but if you are running EE with Duo enabled it belongs on the same change ticket.

Two things GitLab’s release notes do not say: there is no mention of exploitation in the wild, and there is no workaround. The only remedy offered is the upgrade.

CISA says it is already being used

CISA added CVE-2026-85706 to the Known Exploited Vulnerabilities catalog on September 11, the day after the patch. We pulled the feed this morning at catalog version 2026.09.11, which holds 1,709 entries, 225 of them added during 2026.

The KEV entry carries two flags worth understanding. The remediation due date is September 14, 2026, three days, which is the shortest clock the new risk-based directive issues. And forensicTriage is set to Yes, meaning CISA is not asking agencies to patch and move on, it is asking them to go looking for evidence that it already happened.

NVD’s record for the CVE carries CISA’s structured assessment separately, and it is blunter than any press release:

Field Value
Exploitation active
Automatable yes
Technical impact total

“Automatable: yes” is the one to sit with. It means the whole chain, from finding a target to landing the exploit, can run unattended. That is the difference between a vulnerability someone might use on you and one that a scanner will reach on its own schedule.

GitLab.com is not affected in the sense that matters to you: the patch page states plainly that “GitLab.com is already running the patched version” and that “GitLab Dedicated customers do not need to take action.” This is a self-managed problem. If your GitLab lives on a box you rent, it is yours.

The finding: the secrets file is 0644

Here is the part that turns a read into a total loss, and it is not in the CVE, the advisory, or any of the coverage we have read.

A GitLab database is heavily encrypted at rest. CI/CD variables, runner authentication tokens, deploy tokens, integration credentials, two-factor seeds: all of it is stored encrypted, and all of it is decrypted with keys that live in a file on disk. So the honest question about any file read on a GitLab box is: can it reach that file?

Everyone knows about /etc/gitlab/gitlab-secrets.json. We checked how Omnibus writes it, in files/gitlab-cookbooks/package/libraries/helpers/secrets_helper.rb at the 19.3.2+ce.0 release tag, and it is handled correctly:

File.open(path, 'w', 0600) do |f|
  ...
  f.chmod(0600)

Mode 0600, root owned. The Rails application, which runs as the git user, cannot read it. If that were the only copy, the impact of a file read as the application user would be considerably smaller.

It is not the only copy. In files/gitlab-cookbooks/gitlab/recipes/gitlab-rails.rb, at the same release tag, Omnibus writes a second file containing the same material:

templatesymlink "Create a secrets.yml and create a symlink to Rails root" do
  link_from File.join(gitlab_rails_source_dir, "config/secrets.yml")
  link_to File.join(gitlab_rails_etc_dir, "secrets.yml")
  source "secrets.yml.erb"
  owner "root"
  group "root"
  mode "0644"
  sensitive true

gitlab_rails_etc_dir resolves from the packaged default /var/opt/gitlab/gitlab-rails, so the path is /var/opt/gitlab/gitlab-rails/etc/secrets.yml, and it is symlinked into the Rails tree as config/secrets.yml.

Mode 0644. World readable. Not group readable by a service group, not readable by the git user alone. Readable by any process on that host, and reachable by any bug that can name a path.

The same block lists exactly what is in it:

Key What it protects
db_key_base The encryption key for the sensitive database columns: CI/CD variables, runner tokens, deploy tokens, integration credentials
secret_key_base Rails session and cookie signing. Forge a session cookie, become any user
otp_key_base Two-factor authentication seeds
encrypted_settings_key_base Encrypted application settings
openid_connect_signing_key The key GitLab signs OIDC tokens with, if you use GitLab as an identity provider
active_record_encryption_primary_key Active Record encrypted attributes
active_record_encryption_deterministic_key Active Record deterministic encryption
active_record_encryption_key_derivation_salt Active Record key derivation

The sensitive true flag on that resource is a Chef directive that keeps the contents out of the converge log. It does not change the file mode. Somebody was thinking about secrecy in one place and not the other.

So the shape of the incident is this. The lock on the front door is genuinely good. The spare key is under the mat, and it opens the same door.

We want to be careful about the one thing we cannot establish. GitLab says the read works “under certain conditions” and does not say what they are, and the HackerOne report behind it, #3909881, is not publicly disclosed. So we do not know whether an attacker gets a fully arbitrary path or something narrower, and we are not going to pretend otherwise. What we do know is that the packaging leaves nothing in reserve if the path turns out to be arbitrary. There is no second line of defence here made of file permissions, because there are no meaningful file permissions on the file that matters.

The version trap

Now the operational problem, which is worse than the usual “go patch it” story.

Look again at the affected range: 18.7 up to 19.1.8, 19.2 up to 19.2.6, 19.3 up to 19.3.1. Then look at the fixed versions: 19.1.8, 19.2.6, 19.3.2. Those two lists do not cover the same ground.

GitLab’s maintenance policy explains why. Security fixes are backported “to the previous two monthly releases in addition to the current stable release”, which right now means 19.3, 19.2 and 19.1. Everything older gets nothing.

We enumerated GitLab’s release metadata to find out how much “everything older” is. Between 18.7.0 on December 18, 2025 and 18.11.11 on August 17, 2026, GitLab shipped 53 releases across six minor lines: 18.7, 18.8, 18.9, 18.10, 18.11 and 19.0. Every one of them is inside the affected range. None of them has a patch, and none is coming.

If you are on any of those, you are not applying a patch this week. You are doing a version upgrade, and GitLab enforces required upgrade stops that you cannot skip: 18.2, 18.5, 18.8 and 18.11 in the 18 series, then 19.2, 19.5, 19.8 and 19.11 in the 19 series. A shop sitting on 18.9 has to land on 18.11, then 19.2, then continue to 19.2.6 or higher. Each stop is a migration window with a backup before it.

That is the real cost of this CVE for most self-managed installs, and it is why “just patch it” is bad advice to give a small team on a Monday morning. Plan it, but do not let the plan be the reason the box sits exposed for two weeks. Which brings us to the interim control, below.

Also note the vulnerable code has been shipping since December 18, 2025. That is a window of nearly nine months, and the reporter found it through the bug bounty programme, credited to s3ntago. The relevant question is not whether anyone else found it in nine months. It is whether you would know.

Timeline

Date Event
December 18, 2025 GitLab 18.7.0 released. Earliest affected version
September 4, 2026 CVE-2026-85706 reserved by GitLab as CNA
September 10, 2026, 22:52 UTC 19.3.2, 19.2.6 and 19.1.8 released. Eighteen security fixes, two critical
September 11, 2026 CISA adds it to the exploited list. Forensic triage required
September 12, 2026, 03:16 UTC NVD publishes. Still “Undergoing Analysis” as of this morning
September 14, 2026 Federal civilian remediation deadline. Today

Why we think the unpatched population is large

This is the part that should worry anyone who runs GitLab and has not thought about it in a year.

GitLab appeared on CISA’s exploited list twice already in 2026, and neither entry was a new bug:

  • CVE-2021-39935, added February 3, 2026. A server-side request forgery in the CI Lint API. GitLab fixed it in 14.5.2, in December 2021.
  • CVE-2021-22175, added February 18, 2026. A second SSRF, in webhook requests to the internal network. Also 2021.

CISA does not add a vulnerability to that catalog as a history lesson. It adds it because there is evidence of active exploitation. Attackers were successfully exploiting 2021-era GitLab flaws in February 2026, which means there is a real population of self-managed instances that have not been updated in more than four years, sitting on the public internet, being found.

The precedent for what happens to that population is CVE-2021-22205, the GitLab ExifTool remote code execution. CISA added it on November 3, 2021, and it is one of the relatively few entries in the whole catalog where the ransomware field reads Known rather than Unknown. That one went from disclosure to mass exploitation to ransomware, on exactly this kind of forgotten box.

For scale on GitLab’s own disclosure volume: GitLab acts as a CVE Numbering Authority, and we counted 257 CVE records with 2026 identifiers published through it so far this year. Only 145 of those carry titles naming GitLab itself. The rest are open-source projects GitLab covers as a CNA, Wireshark alone accounting for 90. Of the 145 GitLab-product records, CVE-2026-85706 is the highest scored, and the only 10.0. NVD lists five earlier GitLab-affecting CVEs at 10.0, the most recent in September 2024. 2025 topped out at 9.9. A 10.0 here is genuinely unusual, which is a reason to treat this one differently from the monthly stream.

What to do

1. Find out what you are running. On a Linux package install:

sudo gitlab-rake gitlab:env:info

That prints the version along with the Ruby, database and Redis details. On a self-compiled install it is bundle exec rake gitlab:env:info RAILS_ENV=production. If you cannot get a shell, an administrator can read it from the Admin area. If you do not know who can get a shell, that is the actual finding of this exercise and it is worth writing down.

2. If you are on 19.1, 19.2 or 19.3, this is a patch and it should happen today. Go to 19.1.8, 19.2.6 or 19.3.2 at minimum. Nothing newer had shipped as of this morning; we checked the release feed.

3. If you are on 18.7 through 19.0.x, get it off the public internet first, then plan the upgrade. There is no patch for you, the upgrade will take a maintenance window you do not have today, and the exposure is live now. Putting the instance behind a VPN or a zero-trust proxy is a change you can make this afternoon, and it removes you from the automated scanning population entirely. GitLab’s support team publishes an upgrade path tool that will calculate the required stops between where you are and where you need to be. Use it before you start, not halfway through.

4. Decide whether you are rotating, and be honest about it. Patching stops the next read. It does nothing about a read that already happened, and if the keys in secrets.yml went out the door, the attacker does not need the vulnerability any more.

Before you decide, understand what rotation actually involves, because GitLab documents it and it is not a button. Their own procedure for when the secrets are gone requires, among other steps: disabling two-factor authentication for every user so they can sign back in and re-enrol; deleting the rows in ci_group_variables and ci_variables; clearing project, group, instance and runner tokens, after which, in GitLab’s words, “you must register new runners”; clearing the encrypted tokens on pending pipeline jobs; and truncating the integrations and webhooks tables. That is a day, and a visible outage, and a lot of small conversations with people about their 2FA.

That cost is exactly why this decision gets deferred, and why deferring it is how a read becomes a breach three months later. Our position: if the instance was internet-reachable on a vulnerable version at any point since September 10, treat the CI/CD variables and deploy tokens as burned and rotate those first, because those are the credentials that reach your cloud account and your customers’ systems. The 2FA and session rotation can follow on a planned window. We wrote about the same triage problem from the other direction when Stripe keys started turning up in exposed .env files: the credential you rotate late is the one that gets used.

5. Go looking, because CISA is telling agencies to. The forensic triage flag is a signal that evidence exists to be found. Check your reverse proxy and GitLab access logs for unauthenticated requests to repository commits API paths, anything with traversal sequences in a ref or path parameter, and requests from single addresses enumerating many projects. Check for sessions and personal access tokens you cannot account for. Check whether any CI/CD variable has been used from an IP that is not your runner.

If you do not run GitLab

Most of the small businesses we work with do not run a Git server. Somebody else runs one on their behalf, and that is the version of this story that matters to them.

Your source code, your deployment keys and the credentials that reach your payment processor and your database all live somewhere. If you hired an agency, a contractor or a development shop, they live on that company’s infrastructure. A 10.0 on their box is a 10.0 on your business, and you will not get a notification.

Three questions, and they are reasonable to ask this week:

  1. Where does our source code live, and is that system reachable from the public internet? A hosted service like GitLab.com, GitHub or Bitbucket is a perfectly good answer, and it means this particular CVE is not your problem. A self-managed instance is also a fine answer as long as somebody owns patching it.
  2. What version is it on, and when was it last updated? The specific answer matters less than whether they can answer at all. “I would have to check” is fine. A long pause is data.
  3. If that server were read by a stranger tomorrow, which of our credentials would be in the blast radius, and how fast can they be rotated? This is the question that finds the deploy key from 2023 that still works.

This is the same conversation we have recommended after the Gitea diffpatch RCE in August and the N-able N-central compromise before it. The pattern across all three is that the systems holding the keys to everything else are now the primary target, and most of them are operated by somebody two steps removed from the business that would carry the loss.

What we could not verify

In the interest of not repeating things we cannot stand behind:

  • We could not count internet-exposed self-managed GitLab instances. Several outlets have quoted figures. Shadowserver’s public API returned nothing for GitLab when we queried it this morning, and we are not going to launder somebody else’s number into ours.
  • We could not confirm the widely repeated claim that exploitation attempts were observed one day after disclosure. It is plausible and consistent with CISA listing it on September 11, but we found no primary source, and GitLab’s own notes do not mention exploitation at all. What is on the record is CISA’s structured assessment: exploitation active, automatable yes, technical impact total.
  • The exact preconditions are not public. GitLab’s “under certain conditions” is doing unexamined work, and the bug bounty report is not disclosed. It may turn out that the read is narrower than the worst reading. Patch as though it is not.
  • The 0644 finding is from the Omnibus Linux package. We verified it in the packaging source at the 19.3.2+ce.0 tag and at current master. We did not verify the equivalent file modes for the Helm chart, Docker image or self-compiled installs, which lay out configuration differently. If you run one of those, check config/secrets.yml in your own deployment before assuming either the good news or the bad.

The short version

If you run self-managed GitLab on 19.1, 19.2 or 19.3, patch today. If you are on 18.7 through 19.0.x, take it off the public internet today and book the upgrade, because there is no patch coming for your version. Either way, assume the keys in /var/opt/gitlab/gitlab-rails/etc/secrets.yml were readable by anything that could reach the box, and start rotating the CI/CD variables and deploy tokens that reach systems you care about.

And if somebody else holds your source code, send them the three questions. The worst outcome of asking is a slightly awkward email. The worst outcome of not asking is that you find out from a customer.

If you want a second pair of eyes on any of this, get in touch. We are in Toronto, we run this stuff for clients, and we would rather have the conversation now than after.

Sources

  • GitLab critical patch release: 19.3.2, 19.2.6, 19.1.8, GitLab, September 10, 2026, for the security fixes it lists, the per-CVE severities and affected ranges, the quoted upgrade urgency wording, and the statement that GitLab.com is already running the patched version and GitLab Dedicated customers need take no action
  • GitLab’s own CVE record for CVE-2026-85706, retrieved from https://gitlab.com/gitlab-org/cves/-/raw/master/2026/CVE-2026-85706.json on September 14, 2026, for the CVSS 10.0 score and vector, the CWE-22 classification, the quoted description, the affected and fixed versions, and the credit to s3ntago
  • CVE-2026-85706, NVD, published September 12, 2026, for the publication timestamp, the “Undergoing Analysis” status as of September 14, and CISA’s SSVC assessment of exploitation active, automatable yes, technical impact total
  • Known Exploited Vulnerabilities Catalog JSON feed, CISA, downloaded September 14, 2026 at catalog version 2026.09.11, for the September 11 addition, the September 14 due date, the forensicTriage flag, the 1,709 total entries and 225 added during 2026, the February 2026 additions of CVE-2021-39935 and CVE-2021-22175, and the knownRansomwareCampaignUse value of Known on CVE-2021-22205
  • https://version.gitlab.com/api/v1/versions, GitLab’s release metadata API, queried September 14, 2026, for the 2026-09-10T22:52 UTC publication timestamps of 19.3.2, 19.2.6 and 19.1.8, the December 18, 2025 date for 18.7.0, and the enumeration of 53 releases across the 18.7 to 19.0 band. The count is our own analysis of that feed.
  • omnibus-gitlab, files/gitlab-cookbooks/gitlab/recipes/gitlab-rails.rb, read at release tag 19.3.2+ce.0 and at master on September 14, 2026, for the mode "0644" on the secrets.yml templatesymlink resource and the list of eight keys it writes, and files/gitlab-cookbooks/package/libraries/helpers/secrets_helper.rb at the same tag for the 0600 mode on /etc/gitlab/gitlab-secrets.json. The default path /var/opt/gitlab/gitlab-rails comes from files/gitlab-cookbooks/gitlab/attributes/default.rb.
  • Back up and restore GitLab and Troubleshooting backups, GitLab documentation, for the secrets file paths, what the keys protect, and the documented reset procedure when secrets are lost, including the 2FA, CI/CD variable, runner token and webhook steps and the quoted “you must register new runners”
  • GitLab maintenance policy, for the quoted backport rule of “the previous two monthly releases in addition to the current stable release”, and Upgrade paths for the required stops at 18.2, 18.5, 18.8, 18.11 and 19.2, 19.5, 19.8, 19.11
  • GitLab maintenance rake tasks, for the gitlab-rake gitlab:env:info command
  • NVD query for all critical GitLab-affecting CVEs, https://services.nvd.nist.gov/rest/json/cves/2.0?virtualMatchString=cpe:2.3:a:gitlab:gitlab&cvssV3Severity=CRITICAL, retrieved September 14, 2026, for the five earlier entries scored 10.0 and the 2025 maximum of 9.9
  • The count of 257 CVE records with 2026 identifiers published through GitLab as a CNA, and the split of 145 GitLab-product records against 112 third-party ones, is our own analysis of https://gitlab.com/gitlab-org/cves, retrieved September 14, 2026
● Taking new projects

Have something that needs shipping?

One call. Thirty minutes. You leave with an honest read on scope, timeline, and price, whether we're the right fit or not.