Your self-hosted Git server hands out write access by default, and that is now all an attacker needs
CVE-2026-60004 is a 9.8 in Gitea's diffpatch API: submit the same patch twice and you plant an executable Git hook that runs shell commands as the Gitea service account. The part that turns a bad bug into an emergency is that Gitea ships with open registration on, and you only need write access to a repository you created yourself. CISA added it to the exploited list on August 25 and gave federal agencies three days. Shadowserver counts nearly 5,000 exposed instances. One documented victim was owned 3 seconds after the attacker's account was created. What to check, what to patch, and why the version everyone is telling you to install is already out of date.
A self-hosted Git server is the most concentrated pile of value a small technical business owns. It is not one application’s data. It is every application’s source code, every deployment key, every .env file somebody committed in 2023 and never rotated, every CI token, and the OAuth credentials that let the whole thing talk to GitHub, Slack and your cloud account. If an attacker gets shell on it, they do not have a foothold. They have the business.
Gitea is the most popular way to run one. It is a single Go binary, it runs happily on a $12 VPS, and for a five-person shop it is a genuinely good decision. We have recommended it. The problem is not the choice. The problem is a default it ships with, combined with a bug disclosed at the end of July that CISA confirmed on August 25 is being exploited in the wild.
The bug
CVE-2026-60004, CVSS 9.8, tracked by Gitea as GHSA-rcr6-4jqh-j84m. Classified CWE-94, code injection. It affects Gitea 1.17 through 1.27.0, which is every release since 2022, and it was fixed in 1.27.1 on July 27, 2026.
The vulnerable endpoint is POST /api/v1/repos/{owner}/{repo}/diffpatch, the API behind Gitea’s “apply patch” feature. It takes a patch you supply and applies it inside a temporary Git working area.
The mechanism, from Gitea’s own advisory, is worth understanding because it explains why nobody caught it for four years:
- You submit the same patch twice. That creates an add/add collision, because the file the patch adds already exists from the first application.
- Git falls back to its three-way merge path to resolve the collision. That fallback checks out indexed paths to disk, even during an operation that was supposed to stay in the cache.
- In a bare repository clone,
$GIT_DIRis the repository root. So a path ofhooks/post-index-changein your patch is not a file inside a working tree. It is a live Git hook, written into the hook directory. - Git runs
post-index-changewhen it writes the index. Which it is about to do. Your patch content executes as the Gitea OS user.
Nothing here is a memory bug or an exotic parser trick. It is three ordinary behaviours of Git and a path that means something different than it looks like it means. The preconditions are Git 2.32 or newer, the diffpatch route enabled, and a temp filesystem that is writable and executable. On a default install, all three are true.
The default that turns it into an emergency
Read the advisory quickly and you will land on “requires repository write access” and relax, because you know who has write access to your repositories.
That reading is wrong, and it is the single most important sentence in this post: you need write access to a repository, not to one of theirs. An attacker registers an account, creates their own empty repository, and now has write access to it. That is the precondition, satisfied.
Gitea’s [service] section defaults to DISABLE_REGISTRATION = false. Self-registration is on out of the box. So is REGISTER_EMAIL_CONFIRM = false, meaning nothing verifies the address. REQUIRE_SIGNIN_VIEW = false means the instance is browsable, and therefore fingerprintable, without an account.
Put those together and a 9.8 that reads like it needs an insider is, on a default install, pre-authentication remote code execution reachable by anyone who can load the login page. That is exactly how it is being exploited, and public exploit code for it is on GitHub.
What exploitation actually looks like
A developer published a postmortem of their own compromised instance, and it is the most useful artefact in this whole story because the timeline is measured in seconds.
They were running Forgejo v13, which had reached end of life in January 2026. Forgejo is a fork of Gitea and inherits the same diffpatch endpoint and the same bug.
- An automated scanner probed the instance, apparently to read its version, then went away for about 45 minutes.
- It came back and registered an account named
testpoc26188. - Account creation, repository push, and exploit trigger happened within about 3 seconds.
- The second-stage payload downloaded roughly two minutes later.
The payload was a cryptominer dropper. It cleared environment variables, killed competing high-CPU processes and antivirus, detected the CPU architecture, pulled the matching binary with several fallback download methods, executed it in memory and deleted the files behind it. CPU sat above 70 percent, which is how the owner noticed.
What it did not do is the part worth dwelling on. No cron entry, no systemd unit, no SSH key, no persistence at all, and no lateral movement in the gateway logs. This particular operator wanted CPU cycles and nothing else.
You do not get to conclude from that that the risk is a slow server. You get to conclude that the payload is a choice made after the access is already sold. The same three seconds that dropped a miner would have dropped an infostealer. Gitea’s advisory lists what is reachable from that shell: application secrets, database credentials, OAuth tokens, mounted repositories and internal services. The miner is what you get when the person who bought your server was cheap.
The timeline, and why the deadline was three days
| Date | Event |
|---|---|
| July 27, 2026 | Gitea 1.27.1 released with the fix |
| July 28, 2026 | GHSA-rcr6-4jqh-j84m published, credited to Shai Rod (NightRang3r) of Salesforce |
| August 14, 2026 | Gitea 1.27.2 released with seven further CVEs fixed |
| August 25, 2026 | CISA adds CVE-2026-60004 to the Known Exploited Vulnerabilities catalog |
| August 28, 2026 | Federal civilian remediation deadline. Three days. |
Three days is not the old KEV default. It is the output of BOD 26-04, the directive we wrote about in June that replaced flat deadlines with four questions about the vulnerability’s actual character. A pre-auth RCE on an internet-facing service with public exploit code and confirmed in-the-wild use answers all four the wrong way, so it gets the shortest clock CISA issues.
You are not a federal agency and the directive does not bind you. It is still the best free triage signal available, and what it is telling you here is: today, not this sprint.
Shadowserver currently tracks nearly 5,000 Gitea instances exposed to the internet. That number does not tell you how many are patched, and some of it is honeypots. It does tell you the population being scanned is small enough to enumerate completely and repeatedly, which is what the 45-minute gap in that postmortem was.
What to do
1. Check your version, then patch past the version everyone is telling you to install.
Most coverage says upgrade to 1.27.1. That closes CVE-2026-60004 and it is no longer the right answer, because 1.27.2 shipped on August 14 with seven more CVEs, several of which matter on their own:
CVE-2026-73539, argument injection into external renderer commands, another route to RCECVE-2026-73800,pull_request_targetworkflows resolving reusable workflows from the fork’s HEAD instead of the base branch, which hands repository and organisation secrets to attacker-controlled workflow codeCVE-2026-73278andCVE-2026-73535, OAuth2 and OIDC sign-in paths that only checked TOTP enrolment, letting WebAuthn-only accounts through without a second factorCVE-2026-73814, repository collaborators with Admin escalating to OwnerCVE-2026-73804, awrite:usertoken adding its own SSH key to escape token scopeCVE-2026-60008, the Jupyter notebook renderer interpolating notebook language info
Go to 1.27.2. If you are on Forgejo, the fix is in the current releases of v15 LTS and v16. If you are on a Forgejo version that has gone end of life, as the postmortem author was, you are not patching, you are upgrading, and it needs to happen this week.
2. Turn off self-registration, whether or not you are patched.
If your Git server serves your own team, nobody should be able to create an account on it. In app.ini:
[service]
DISABLE_REGISTRATION = true
REQUIRE_SIGNIN_VIEW = true
DISABLE_REGISTRATION means only an admin creates accounts. REQUIRE_SIGNIN_VIEW forces a login before any page or API call, which also stops the version fingerprinting that starts every one of these campaigns. If you genuinely need open signup, set REGISTER_EMAIL_CONFIRM = true at minimum, and understand that it slows an automated attacker down rather than stopping one.
This is not a fix for CVE-2026-60004. Patching is the fix. This is the control that would have made you a non-target for the next diffpatch-shaped bug, and there will be one.
3. Get it off the public internet if it does not need to be there.
A five-person team’s Git server does not need a public IP. Put it behind a VPN or a zero-trust proxy and the entire class of “automated scanner finds your version banner” disappears. The usual objection is CI and webhooks, and it is usually solvable: your runner can live on the same private network, and most webhook consumers can be flipped to outbound polling.
4. Check whether it already happened.
Even if you patch this afternoon, the window has been open since July 27 at the latest. Look for:
- Accounts you did not create, especially recent ones with machine-generated names
- Repositories created and pushed to within seconds of the account being made
POSTrequests todiffpatchin your Gitea or reverse proxy access logs- Executable files in any repository’s
hooks/directory that you did not put there,post-index-changein particular - Processes owned by the Gitea service account that are not Gitea or Git
- Unexplained CPU load or outbound connections
If any of that turns up, the server is not the incident. The credentials on it are. Rotate everything reachable from that shell: database passwords, OAuth apps and their secrets, deploy keys, CI tokens, webhook secrets, and any secret committed into any repository on the instance, including in history. Then rebuild the host rather than cleaning it, for the same reason we gave when self-hosted AI tooling kept landing on the KEV list: once arbitrary code has run as the service account, you cannot prove what is left.
The wider pattern
We have now written four times this year about the same shape of problem. Langflow, n8n, Ray, MLflow and Metabase on CISA’s exploited list. Metabase’s SQL injection zero-day. N-able N-central. Now Gitea.
The common thread is not that self-hosted software is bad. It is that the internal developer tool has quietly become the softest internet-facing surface most technical businesses have. Nobody put it in the asset inventory. Nobody assigned it an owner. It got installed during a project, it worked, and it has been running unattended ever since on whatever version was current that month. It has no vendor emailing you about patches because there is no vendor, and no invoice arriving to remind you it exists.
The fix is boring and it is the same every time. Write down what you self-host. Give each thing an owner and a way to hear about its releases. Do not leave open registration on. Do not put it on a public IP unless it has to be there. That is an afternoon, once, and it is the difference between reading this post and reading your own logs.
If you self-host Gitea, Forgejo, or anything else in that category and you are not sure what version it is on or who is responsible for it, get in touch. Checking is a short job. It is a much shorter job than the alternative.
Sources
- GHSA-rcr6-4jqh-j84m: Remote Code Execution via diffpatch Git Hook Installation, Gitea security advisory, published July 28, 2026, for the mechanism, affected versions, CVSS vector and credit
- Gitea 1.27.1 is released, Gitea blog, July 27, 2026
- Gitea 1.27.2 is released, Gitea blog, August 14, 2026, for the seven additional CVEs
- Critical Gitea vulnerability now exploited in the wild (CVE-2026-60004), Help Net Security, August 26, 2026
- Hackers now exploit critical Gitea flaw in code injection attacks, BleepingComputer, for the Shadowserver exposure count and CISA deadline
- Critical Gitea RCE Actively Exploited as Reported Attack Drops Miner-Like Payload, The Hacker News, August 2026
- My Homelab Got Hacked, A Postmortem, for the exploitation timeline, the
testpoc26188account and the payload behaviour - Configuration Cheat Sheet, Gitea documentation, for the
[service]defaults - Known Exploited Vulnerabilities Catalog, CISA, for the August 25, 2026 addition