Blog
GitLabCVE-2026-19478GraphQLSupply Chain Security

GitLab CVE-2026-19478: An Unauthenticated GraphQL Directive Can Delete Your Repos

GitLab's out-of-band patch fixes CVE-2026-19478 (CVSS 9.4): an unauthenticated GraphQL directive that can modify or delete public projects and user data. Who is affected, and how to fix it.

Zero Hunt Research··9 min read

On Monday 17 August 2026, GitLab shipped an out-of-schedule patch release for a single reason: CVE-2026-19478, a critical flaw that lets an unauthenticated attacker "remotely modify or delete public projects and user data via a GraphQL directive." No login. No user interaction. GitLab.com and GitLab Dedicated were already running the fixed build before the advisory went out — which means the whole burden of this one falls on self-managed installations, the internet-exposed instances that sit at the root of most organisations' software supply chain.

The CVSS score is 9.4. The number that matters more is buried in the vector string.

What CVE-2026-19478 actually is

GitLab has published the shape of the bug but not the mechanism. Per the patch-release advisory, the flaw "under certain conditions could allow an unauthenticated user to remotely modify or delete public projects and user data via a GraphQL directive." The full technical write-up is embargoed for roughly 90 days — the standard GitLab disclosure window — so expect the exploitation conditions to become public around mid-November 2026. Defenders do not get to wait until then.

The CVSS vector is CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H. Read it left to right and the severity is not really about theft:

Metric Value What it means here
Attack Vector Network Reachable over the exposed web interface
Attack Complexity Low No special conditions the attacker has to win
Privileges Required None Unauthenticated
User Interaction None No victim has to click anything
Confidentiality Low Little data read
Integrity High Data can be changed
Availability High Data can be destroyed

Confidentiality is the low one. Integrity and availability are high. This is not a data-theft bug — it is a sabotage bug. An attacker who lands it does not quietly exfiltrate your source; they alter it or delete it outright, and the first signal you get is a project that used to exist and no longer does.

The same release also closed CVE-2026-19650 (CVSS 7.1), a cross-site request forgery in the GraphQL multiplex query handler that let an unauthenticated user "execute mutations via GET requests due to improper request validation." Two GraphQL bugs in one emergency release is not a coincidence — it is a signal about where GitLab's exposed attack surface currently lives.

Why "delete public projects" is worse than a leak

Security teams are trained to fear exfiltration. A double-extortion crew steals your repositories, threatens to publish them, and you spend a bad quarter. That is survivable because the data still exists — you have it, they have a copy.

Integrity and availability attacks on a source-control server are a different failure mode. The SCM is the root of trust for everything downstream: your CI/CD pipelines pull from it, your artifact builds are defined by it, your infrastructure-as-code lives in it. An unauthenticated actor who can modify a project can:

  • Slip a malicious commit into a public repository that thousands of downstream consumers pull.
  • Rewrite pipeline definitions so the next build ships a backdoor — the TeamCity pre-auth RCE we covered earlier this month is the same lesson from the CI side of the same pipeline.
  • Delete history, tags, or whole projects to cover tracks or to inflict pure operational damage.

"We assumed the blast radius of a GitLab bug was 'someone reads our private code.' We had no runbook for 'someone deleted the public project that three hundred customers build against.' That is not an incident-response problem. That is a rebuild-from-backups problem, and we found out our backups of project metadata were three days stale."

That counterfactual is the one worth war-gaming. Most organisations can restore a repository's Git objects. Far fewer can cleanly restore the surrounding GitLab state — issues, merge-request history, tokens, membership — to a known-good point, and even fewer have ever tested it.

GitLab bugs get weaponised fast

The reason to treat an unproven, not-yet-exploited GitLab CVE as urgent is the base rate. In January 2024, GitLab disclosed CVE-2023-7028, a CVSS 10.0 account-takeover flaw where a password-reset email could be sent to an attacker-controlled address. It was unglamorous — a one-line logic error in the reset flow. Within months it was under active exploitation and CISA added it to the Known Exploited Vulnerabilities catalog. The victims were, overwhelmingly, self-managed instances that had not upgraded.

The pattern repeats because self-managed GitLab has a structural lag: the SaaS platform gets the fix on day zero, and the long tail of on-prem instances upgrades over weeks or months. Attackers know the advisory is a starting gun. For an unauthenticated, network-reachable, low-complexity bug on a mass-deployed platform, the window between "patch published" and "public exploit" is the interval a scanner needs to reverse-engineer the fix — and GraphQL diffs are not hard to read.

CVE-2026-19478 has no public exploit as of this writing. That is the good news and the deadline in the same sentence.

The GraphQL directive problem

Directives are the part of GraphQL most teams never audit. They are the @-prefixed modifiers — @skip, @include, @defer, and any custom ones a schema defines — that change how a query is resolved rather than what it asks for. Because they operate as a cross-cutting layer over the resolver graph, they are a recurring home for authorization-bypass bugs: if the directive logic runs in a place where the authenticated-user context is not what the resolver assumes, a request can end up executing with the wrong authority.

The @defer and @stream directives, in particular, split a single query into incrementally delivered chunks. That splitting is exactly the kind of control-flow seam where "the auth check happened, but not on this branch" errors live. GitLab has not confirmed which directive is implicated in CVE-2026-19478, and we are not going to guess in public — but the class is well understood, and "unauthenticated mutation reachable through a directive" is squarely inside it. The companion CSRF bug executing mutations through GET requests points at the same theme: state-changing operations reachable through request paths that were never supposed to carry them.

If you run GraphQL anywhere in your own stack, this is the reminder to check that authorization is enforced in the resolvers themselves, not in a layer that a directive can route around.

Remediation

CVE-2026-19478 has a clean fix, and the exposure is straightforward to reason about. Do this in order.

1. Am I affected?

Check your GitLab version — self-managed only; GitLab.com and Dedicated are already patched.

# On the GitLab host
gitlab-rake gitlab:env:info | grep -i version
# or, from any instance
curl -s https://<your-gitlab>/api/v4/version   # requires a token
# or read the footer / help page: /help

Affected: GitLab CE and EE 18.2 through the unpatched 18.11 / 19.0 / 19.1 / 19.2 lines. If you are on an 18.2-or-later release below the fixed builds, you are exposed. Instances reachable from the public internet are the priority; but "internal only" is not a safe assumption for an unauthenticated bug once an attacker has any foothold on the network.

2. Patch — exact fixed versions

Upgrade to the nearest fixed release on your branch, verbatim from the advisory:

  • 19.2.4
  • 19.1.6
  • 19.0.8
  • 18.11.11

There is no configuration workaround that GitLab endorses. The patch is the fix.

3. Can't patch this hour? Compensating controls

Buy time, do not substitute for the upgrade:

  • Put the GitLab web interface behind authentication at the edge (VPN / reverse-proxy allow-list) so unauthenticated internet traffic cannot reach /api/graphql at all.
  • At the WAF or reverse proxy, block or heavily rate-limit unauthenticated POST and GET requests to the GraphQL endpoint — the companion CVE-2026-19650 abuses GET, so a POST-only rule is insufficient.
  • Disable public visibility on projects you cannot afford to lose while you patch, reducing the "public projects" the bug explicitly targets.

4. Hunt for compromise

Because the bug modifies or destroys data, your evidence is in the audit and application logs, not in exfil telemetry. Map to MITRE ATT&CK:

  • T1190 — Exploit Public-Facing Application: anomalous POST/GET to /api/graphql from unauthenticated sessions, especially requests carrying directives on mutation operations.
  • T1485 — Data Destruction / T1565 — Data Manipulation: project or repository deletion and settings changes with no corresponding authenticated user in the audit event. Review GitLab's audit events and the application_json / production_json logs for mutation activity that does not tie back to a real actor.
  • T1195 — Supply Chain Compromise: unexpected commits, tag rewrites, or CI/CD configuration changes on public projects. Diff current pipeline definitions against a known-good baseline.

IOCs will be thin until the technical disclosure lands — which is precisely why behavioural detection (a data-changing request with no authenticated identity behind it) beats waiting for a signature.

5. Eradicate and verify

If you find evidence of tampering:

  • Restore affected projects and metadata from a backup predating the earliest suspicious GraphQL activity — and confirm your restore actually covers issues, MR history, and membership, not just Git objects.
  • Rotate anything an attacker with write access could have seen or planted: CI/CD variables, deploy tokens, runner registration tokens, and any secrets committed to affected repos.
  • Re-verify integrity after patching, not before — an unpatched instance can be re-tampered the moment you finish cleaning it.

Where Zero Hunt fits

The uncomfortable part of CVE-2026-19478 is timing. The patch is out, the exploit is not — yet — and the only question that matters is whether your GitLab is exposed right now, on the version it is actually running, behind the network controls you actually have. A quarterly pentest answers that question for a version you retired two months ago.

Zero Hunt's 10-agent generative engine is built to answer it continuously. Its change-triggered campaigns treat a new advisory the way an attacker treats it: as a starting gun. When a GitLab instance appears on the perimeter, or an advisory lands for software you run, the Recon and Web agents fingerprint the exposed version and the Exploit agent writes a per-target validation chain — generated locally, unique to your environment, not pulled from a public PoC that does not exist yet. You learn whether you are exploitable from your own evidence, not from a CVSS estimate, and you learn it in the hours that matter rather than at the next audit.

Because this is an integrity-and-availability bug, the second half of the answer is evidentiary. Every finding, every validated exposure, and every remediation is mapped against the 32 compliance frameworks Zero Hunt tracks and ECDSA-signed at write time, so when an auditor — or an incident lawyer — asks "prove your GitLab was patched and clean before the disclosure window closed," you have a chain-of-custody record that says exactly when, not a screenshot. For a bug whose whole danger is that data can be silently changed or deleted, a tamper-evident log of what your own systems looked like, and when, is not a compliance nicety. It is the forensic baseline you will wish you had.

The engine runs entirely on-premise — no cloud callbacks, no external LLM APIs, your source-control estate never leaves your network. For the same reason a source-control server is your supply-chain root of trust, the thing that validates it should not be pushing your topology to someone else's cloud. See how the continuous validation model works, or read the companion case from the CI side of the pipeline in our TeamCity CVE-2026-63077 and Gitea CVE-2026-20896 write-ups.

Patch to 19.2.4, 19.1.6, 19.0.8, or 18.11.11 today. Then find out — from evidence, not estimate — whether anyone got there first.