TL;DR
CVE-2026-60004 is a code-injection flaw (CWE-94, CVSS 9.8) in Gitea’s diffpatch API endpoint. A user with repository write access submits a crafted patch, and Gitea writes an executable Git hook into the live hooks directory and runs it as the Gitea service account — arbitrary shell execution. The catch: Gitea ships with open self-registration enabled by default, so an anonymous visitor can sign up, create a repository, and reach the vulnerable code path without stealing anyone’s credentials. CISA added it to the KEV catalog on August 25, 2026, with a federal remediation due date of August 28. If you run Gitea 1.17 through 1.27.0, you are in the blast radius.
This is the bug that matters for anyone who self-hosts Git. Not because Gitea is the biggest Git platform — it’s not — but because it’s the one that ends up on the internet with default settings, holding source code, CI credentials, deployment keys, signing material, and container images.
What Actually Breaks
The vulnerable path is POST /api/v1/repos/{owner}/{repo}/diffpatch, the endpoint that applies attacker-controlled patches inside a temporary repository clone. The mechanism, per Gitea’s own advisory:
- The attacker submits the same patch twice, creating an add/add collision.
- Git’s three-way merge fallback checks the indexed path out into the working tree even though the operation ran with
--cached. - The temporary clone is a bare repository, so its root is
$GIT_DIR— and the file lands athooks/post-index-change. - When Git updates the index, it invokes
post-index-changeas a live hook. The attacker’s shell command runs as the Gitea OS user (typically thegitaccount). - The hook’s exit value is not propagated back to the
diffpatchHTTP response — so the endpoint can look like a normal, successful patch apply.
The 1.27.1 fix changes the temporary clone from bare to non-bare, which removes the condition that let the patched-in file land in the hook directory.
Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H (9.8 — Critical). The “scope changed” bit is the part that makes this worse than a typical authenticated RCE: the compromise can reach into data and services the Gitea process can touch, not just the repository.
Why “Authenticated RCE” Is the Wrong Label
Most writeups call this “a user with write access can get RCE.” Technically true. Operationally misleading.
The privilege barrier is repository write access, not authentication. And Gitea’s default configuration hands that to anyone who registers. Open self-registration is the shipped default, and new users can create their own repositories. So the full unauthenticated chain is:
anonymous visitor
→ self-register a normal account (open registration, default)
→ create a private repository (default repo-creation allowed)
→ POST a crafted patch to /diffpatch (twice, for the add/add collision)
→ hooks/post-index-change executes (as the Gitea service account)
No stolen credentials. No compromised developer. No supply-chain compromise of a package. A stranger with a browser and the Gitea instance’s URL.
That is the difference between a vulnerability you can defer and one you treat as an active incident. “You need an account” is not a meaningful barrier when the platform creates accounts for strangers on purpose.
The Patch-Labeling Trap
This is the detail that makes me uncomfortable, and it’s worth repeating for anyone who tracks release notes:
The CVE-2026-60004 fix is listed under a “MISC” heading in the 1.27.1 release notes as “refactor: git patch apply” — not under “SECURITY.” The same release reserves its SECURITY section for an unrelated two-factor-auth fix on OAuth2 endpoints.
If your process is “watch for security-labeled changes,” you will miss this. A critical RCE in the release notes that doesn’t say “security” is a trap. The only reliable signal is the version: 1.27.1 is the floor. Gitea 1.27.2 shipped on August 14, so install the current 1.27.x build, not just the minimum. And if you’re pinned to an older line (1.26.x, 1.25.x), there is no patched release in that family — the affected range starts at 1.17 and ends only before 1.27.1. You have to move to the 1.27 release family. Verify the running binary, not the package-manager status; a pinned repo or internal mirror can report “latest available” while still serving an affected build.
What to Do
- Inventory every Gitea instance. Internal mirrors, CI sandboxes, demo boxes, Docker containers, third-party package builds — each delivery path can lag upstream differently.
- Confirm the running version against the 1.17–1.27.0 affected range. Check the Gitea admin UI and the binary itself.
- Upgrade to 1.27.1 or later (prefer 1.27.2). Confirm every node and container was replaced, not just the entrypoint.
- Restrict self-registration on internet-facing instances until the upgrade is done. This removes the no-prior-account path — but it is a compensating control, not a fix. A compromised developer account, a malicious insider, or any existing writer can still satisfy the write-access requirement.
- Hunt for prior abuse. Search API and reverse-proxy logs for repeated requests to
/api/v1/repos/{owner}/{repo}/diffpatch, especially from freshly created accounts or repositories made immediately before the activity. Then look beyond web logs: unexpected processes, startup items, scheduled tasks, new service definitions, modified deployment scripts, and unexplained outbound connections running as the Gitea service account. - Rotate secrets reachable by Gitea if there is any evidence of exposure —
app.iniand application secrets, environment secrets, mounted repositories, database credentials, OAuth/integration tokens, and anything the service account can reach on the host.
The Pattern
This is the same shape as the KEV entries I’ve covered recently — a self-hosted or enterprise product with a high-value target, a tight CISA window, and exploitation landing in the wild days after the advisory. vCenter’s Syslog path traversal, TeamCity’s deserialization RCE, Cisco ASA/FTD. The Gitea case adds one more lesson to the list: the default configuration is part of the vulnerability.
A self-hosted developer platform that enables open registration by default has turned “you need write access” into “you need a URL.” That is a design decision with security consequences, and it applies far beyond Gitea. When you self-host anything, the shipped defaults are your real attack surface — not the CVE. Audit what strangers can do by default before you audit what they can do by exploitation.