Back to Blog

Cloning the repository is not a GitLab backup

Cloning the repository is not a GitLab backup

On 17 August GitLab shipped four out-of-band releases. The flaw behind them lets someone, under certain conditions and with no account at all, modify or delete public projects. When you tell people, the answer is almost always the same: "we have the code cloned on every laptop." True. And it is the part you are least likely to lose.

Four releases on a Monday afternoon

On 17 August 2026 GitLab shipped 19.2.4, 19.1.6, 19.0.8 and 18.11.11 as a critical patch release, outside its usual security update schedule. The one in charge is CVE-2026-19478, titled "Code Injection issue via GraphQL directive impacts GitLab CE/EE", critical, CVSS 9.4, vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H. The vendor description, in full: "GitLab has remediated an issue that under certain conditions could allow an unauthenticated user to remotely modify or delete public projects and user data via a GraphQL directive." It was reported by hiimguardian through HackerOne.

It affects CE and EE from 18.2 before 18.11.11, from 19.0 before 19.0.8, from 19.1 before 19.1.6 and from 19.2 before 19.2.4. The same batch carries CVE-2026-19650 (high, 7.1), a CSRF weakness in the GraphQL multiplex query handler that "under certain conditions could have allowed an unauthenticated user to execute mutations via GET requests due to improper request validation". That second one needs a logged-in user to take the bait — its vector carries UI:R — which the first one does not.

Two things worth saying even though they deflate the story. As of 17 and 18 August there is no evidence of active exploitation and no public exploit code; anyone selling urgency here with the words "actively exploited" is making it up. And "under certain conditions" is in the original sentence: GitLab does not say which ones, and we are not going to fill that gap. What is firm is who has to do the work. GitLab.com and GitLab Dedicated were already running the patched version; for self-managed instances the instruction is to upgrade "immediately".

What a clone does not take with it

The headline says "delete projects" and your mind goes to the code. And the code is, of everything inside a GitLab, the only thing replicated by design: every laptop on the team holds the tree and the history.

With one exception almost everyone misses: not the runners. GitLab CI does a shallow clone by default, with a depth of 20. What sits on the machine that builds your image is twenty commits.

Lives in every clone Lives only on the server
Commits, branches and tags Issues and their discussion
The full history of the tree Merge requests, reviews and approvals
The .gitmodules file and the submodule pointer CI/CD variables and their values
  Container and package registries
  Job artifacts and logs
  Wikis (a separate git repository)
  Git notes (refs/notes, not cloned by default)
  Branch protection, environments, webhooks
  Members, permissions, tokens and registered runners

Two rows in that table deserve a note. Wikis are separate git repositories: nobody clones project.wiki.git by hand, so the documentation the team wrote next to the code sits on the server and nowhere else. And the submodule: your clone holds a forty-character pointer, not the content, and the repository it points at may be another one of the projects just deleted.

The damage of a deletion is not measured in repositories. It is measured in why that decision was made, who approved it, which variable deploys to production and which exact image is running right now. None of that is on anybody's laptop.

There was another one in June, and it had no CVE

This is the precedent almost no coverage of the August flaw mentions, and it is the one that best explains the underlying problem. In June, Yuhang Wu, of depthfirst, chained two memory corruption bugs in Ruby's Oj JSON parser through Jupyter notebook diff rendering, and got command execution on self-managed instances. No administrator needed: any authenticated user who could push to a project. It affected 15.2.0 to 18.10.7, 18.11.0 to 18.11.4 and 19.0.0 to 19.0.1. It was fixed on 10 June 2026 in 18.10.8, 18.11.5 and 19.0.2, and the proof of concept was published on 24 July.

No CVE at all. And there is a detail here that is easy to tell wrong, so here it is in full: according to The Hacker News, the researcher himself said depthfirst did not request CVE identifiers for either issue, and GitLab did not answer that outlet's questions about why the fix was not classified as a security problem. You do not need to assume bad faith from anyone to reach the uncomfortable conclusion.

The conclusion is that if your patching process is triggered by CVE bulletins, that fix went past you without turning on a single light. An instance that stayed on the 19.0 branch without reaching 19.0.2 accumulated, between June and August, a path to command execution with any ordinary account and a project deletion with no account at all. A CVE reader would have flagged neither. What does flag them is knowing which version you are on and comparing it with the latest, which is far more boring work. It is the same discipline we wrote about for our own deployments: a tag is not a version.

The key is not where the backup is

Suppose it happens. gitlab-backup create does more than people think: the database and its configuration, repositories and LFS objects, CI/CD job artifacts and logs, account and group settings, the package registry and container registry images, project and group wikis, attachments and uploads, and a few that surprise people, such as Terraform states, Pages content and snippets.

And it does not include GitLab's configuration files (/etc/gitlab, with gitlab.rb and gitlab-secrets.json), TLS and SSH keys and certificates, Redis — and with it the Sidekiq job queue — global server hooks and file hooks, or object storage on standard Linux package, Docker or self-compiled installations. That last one deserves its own warning: if your artifacts, uploads, packages and LFS objects live in a bucket, your GitLab backup does not carry them inside.

The painful one is gitlab-secrets.json, and it is worth being precise about why. It is not that the file contains your CI/CD variables: those are encrypted in the database, which the backup does include. What the file holds is the key that decrypts them. The documentation makes the argument in a sentence that deserves to hang in more offices: storing encrypted information in the same place as its key defeats the purpose of using encryption in the first place. And it describes the consequence of losing it plainly: "Without the keys, multiple issues occur, including loss of access by users with two-factor authentication enabled, and GitLab Runners cannot sign in."

On a Tuesday morning it looks like this: you restore, the database comes back whole, the repositories are there, the interface starts, and the deployment variables are unreadable, the runners cannot sign in, and everyone with two-factor is locked out. It is a first cousin of a dependency we have written about: the backup server that depends on the thing it protects you from.

That leaves the other requirement, in the same documentation: "You can only restore a backup to exactly the same version and type (CE or EE) of GitLab on which it was created." The same version and the same edition. Which gives you an order of operations better written down beforehand than discovered halfway through:

  1. Install the exact version — and the exact edition — the backup was made on.
  2. Put the secrets and the configuration back in place.
  3. Restore the backup.
  4. And then upgrade to the patched version.

Step one is uncomfortable reading: if the disaster catches you out of date, the way back runs through standing the vulnerable version up again. That is not a reason to skip the restore. It is a reason to keep that machine off the public internet while the work lasts, and to keep step four in the same document as step one. Before all of that there is a dumber, more urgent question: which exact version were you running last night? If it is not written next to the backup file, the restore starts with archaeology.

What we would do this week

  • Check the version. In the UI, /help. On a Linux package install, sudo gitlab-rake gitlab:env:info. Write it down — with the edition, CE or EE — wherever the backup lives, not in somebody's head.
  • Upgrade to 19.2.4, 19.1.6, 19.0.8 or 18.11.11, depending on the branch.
  • Count the public projects for real, not the ones you remember. If something you did not expect shows up there, that is the finding of the day, patch or no patch.
# Count page by page: 100 on the first page means there are more
for p in $(seq 1 20); do
  n=$(curl -s "https://TU-GITLAB/api/v4/projects?visibility=public&simple=true&per_page=100&page=$p" | jq 'length')
  echo "page $p: $n"; [ "$n" -lt 100 ] && break
done
  • Decide what has to be visible from the internet: the whole instance, the public project listing, neither. In December 2024 Cycode found over 40,000 self-hosted GitLab servers through Shodan; the number is eighteen months old and the habit has not changed.
  • Get the secrets off the machine. gitlab-secrets.json and gitlab.rb outside, encrypted, with the same discipline as the big backup. On the same disk as GitLab they are not a copy.
  • Rehearse the restore on a clean machine until you see a green pipeline. Not until the interface starts: until a real job finishes and a runner has managed to sign in.
  • Check where object storage lives. If it is outside, it gets backed up separately and written into the same document, because on the bad day nobody remembers.

What we are not going to recommend

Moving to the managed service over this. GitLab.com was patched before anyone found out, and as an argument that is honest and worth acknowledging. But migrating an entire platform to avoid applying patches trades a job you know for one you do not yet; and the reasons somebody self-hosts — where the code lives, what it integrates with, what it costs — are not repealed by a security bulletin.

Putting the whole thing behind the VPN without looking. It is the reflex reaction and sometimes it is the right one, but it breaks things somebody is using right now: inbound webhooks, external runners, third-party integrations, published pages. If you do it, you do it with the list of what breaks in front of you and a warning to whoever uses it, which is more work than typing a firewall rule. It is the same chain of small details we went through when looking at how a secret slips into a pipeline.

And the conflict of interest has to be declared, because we are not talking from the stands here: we run self-hosted GitLab for our own CI/CD and to deploy this very website. The list above applies to us, on the same calendar and with the same rush.

Two questions you do not have to answer in public

The tool that deploys everything else is, in almost every company we know, the only one with no maintenance window of its own, no written restore plan and no rehearsal. It gets updated when it does not bother anyone, which is never, because somebody is always deploying.

So, two questions, and there is no need to answer them out loud: how long does your team take to patch the tool it deploys everything else with? And, without going to look: which exact version would you have to install to restore last night's backup?

Sources (verified on 19 August 2026): the releases published on 17 August 2026, the identifiers CVE-2026-19478 and CVE-2026-19650, their titles, severities, CVSS scores and vectors, the descriptions quoted verbatim, the affected version ranges, the reporters hiimguardian and kreep, the instruction to upgrade "immediately" and the fact that GitLab.com and GitLab Dedicated were already running the patched version, from GitLab Docs, "GitLab Critical Patch Release: 19.2.4, 19.1.6, 19.0.8, 18.11.11". The absence of known exploitation and public exploit code as of 17-18 August, from The Hacker News. What the backup archive does and does not include, from "Back up GitLab"; the requirement to restore onto the same version and edition and the sentence about the keys, from "Restore GitLab". The June chain (Yuhang Wu / depthfirst, the Oj parser and Jupyter notebook diffs), the affected versions and the fixes released on 10 June 2026, the proof of concept published on 24 July, the fact that depthfirst did not request CVE identifiers and that GitLab did not answer questions about the classification of the fix, from The Hacker News. The more than 40,000 self-hosted GitLab servers located through Shodan, from Cycode Labs, published on 15 December 2024 and cited with its date. GitLab CI's default clone depth (20) and the fact that wikis are separate git repositories, from GitLab documentation. Ours: the reading that what a deletion destroys is context rather than code, the table of what a clone holds and what it does not, the order of operations for a restore, the argument that patching by CVE bulletin misses fixes with no CVE, the checklist, and the two positions under "what we are not going to recommend".

When was that restore last tested?

In the disaster recovery plans we write, the restore rehearsal carries a date and a result, not good intentions. And when we take over somebody's infrastructure and cloud, the version inventory is one of the first things we put in writing, because it is what decides how long a bad day lasts.

Talk to everyWAN

Tags:

Share:

Subscribe to our newsletter

To receive IT stories, everyWAN news and exclusive subscriber offers, sign up to our mailing list

Minorisa de Sistemas Informaticos y Gestión S.L. © 2026
everyWAN
everyWAN