Gitea CVE-2026-60004: Critical Git Hook RCE Now Exploited in the Wild
CVE-2026-60004 has put Gitea in the spotlight over the past week. On July 27, 2026, the maintainers shipped version 1.27.1 with a fix for a code injection vulnerability in the `diffpatch` endpoint of the REST API. Three days later, the formal advisory landed along with a proof-of-concept exploit. On August 25, CISA added the flaw to its Known Exploited Vulnerabilities (KEV) catalog under Binding Operational Directive 26-04, giving U.S. federal agencies a 72-hour window to remediate. As of this writing, Shadowserver counts more than 8,393 IPs still running vulnerable Gitea instances, and at least one real intrusion has been publicly documented, ending in a cryptominer-style dropper. If you operate a self-hosted Gitea server reachable from the internet, this article is for you.
What actually happened
The advisory is blunt. Gitea describes the bug as follows: "the diffpatch endpoint can be abused to install and execute a Git hook from repository-controlled content. An attacker with ordinary write access to a repository can execute arbitrary shell commands as the Gitea OS user." In other words: someone with permission to push changes to a repository can plant a malicious hook and force Git to run it, with the full privileges of the Gitea process. CVE-2026-60004 carries a CVSS 3.1 score of 9.8, the maximum critical band.
The affected range is wide: every version from 1.17 through 1.27.0 inclusive. Gitea shipped the fix in 1.27.1 on July 27. A week later, on August 27, the maintainers released 1.27.2 with additional hardening; both versions close the door. The researcher credited with the discovery is Shai Rod (NightRang3r), working at Salesforce. The public advisory, tracked as GHSA-rcr6-4jqh-j84m, includes proof-of-concept exploit code, which means the barrier to reproducing the attack is essentially zero.
The detail that turns a "high" CVE into an emergency is the default configuration. Gitea ships with open registration enabled out of the box: it does not require email confirmation, does not require manual approval, does not mark new users as restricted, and does not impose a default repository-creation limit. Any anonymous visitor can register an account, create a repository, and obtain the write access needed to reach the vulnerable endpoint. In practice, the "write access" prerequisite becomes a formality on any installation that has not been hardened.
Technical breakdown of the exploit
The vulnerable endpoint lives at `POST /api/v1/repos/{owner}/{repo}/diffpatch`. Its job is to receive a unified-diff patch from the client and apply it to a temporary clone of the repository. The vulnerable code creates that clone as a bare Git repository and invokes `git apply` with a specific combination of flags: `--index`, `--recount`, `--cached`, `--binary`, and the `-3` three-way fallback option whenever the server runs Git 2.32 or newer.
That combination is where the crack opens. A bare repository has no working tree; the repository directory itself acts as the Git data directory. Any path resolved by Git during patch processing can therefore collide with structural paths like `hooks/`. When the attacker submits the same patch twice in rapid succession, the `-3` mode forces resolution of an add/add conflict. The three-way fallback writes the resulting file to disk to resolve the collision, and if that path falls under `hooks/post-index-change`, Git treats it as a legitimate post-index-change hook and runs it automatically the next time it updates the index. The attacker-controlled content runs as a shell script with the privileges of the user account running Gitea.
The public PoC, credited to the researcher imbas007, automates this flow. The authenticated attacker POSTs the malicious patch to the endpoint; Gitea applies it in the temporary bare clone; Git materializes the hook while resolving the conflict; the hook executes on the next `git update-index` cycle. The entire chain, from first POST to command execution, completes in seconds. SOC Prime's analysis confirms that the 1.27.1 fix replaces the temporary bare clone with a non-bare clone backed by a working tree, which physically separates the `hooks/` path from patch-controlled content and closes the route.
Why this matters in a self-hosted environment
The real risk is not the RCE in isolation but what follows. A Gitea server in a typical organization has visibility into the internal network, access to shared storage, and proximity to the secrets that drive daily operations: deployment tokens, SSH keys for runners, container registry credentials, OAuth integrations with cloud providers, webhooks carrying long-lived tokens. The incident report published by developer Andrey (@Causelof) on Habr describes exactly this scenario. Their hosting provider, HOSTKEY, alerted them that the virtual machine had been running above 70 percent CPU utilization for hours. On investigation, the RCE had been used to deploy a dropper with cryptominer behavior. The attacker code first wrote a proof of RCE into a Git branch, then downloaded a universal shell-loader, then the miner payload itself. The active portion of the attack lasted about eleven seconds. The container was unprivileged and the payload did not survive a restart, but that was a matter of luck, not design.
Gitea's recent history does not inspire calm either. In July, CVE-2026-20896 (CVSS 9.8), an authentication bypass in official Docker images with reverse proxy auth headers, was actively exploited thirteen days after disclosure. In May, CVE-2026-27771 exposed private container registries on more than 30,000 deployments. The pattern is consistent: a critical CVE lands every few months, and the community takes time to patch.
How to detect a compromise
Gitea did not publish formal detection guidance, but there are clear signals to review. Start with the hook tree of every repository. Find the data directory, typically `/var/lib/gitea/data/gitea-repositories/`, and inside each repository look at `hooks/post-index-change` and `hooks/post-checkout`. Any file present in those paths that you did not create yourself is a candidate injected hook. Also check `hooks/pre-receive`, `hooks/post-receive`, `hooks/update`, and any symlinks under `custom_hooks/`. Modification timestamps that line up with quick responses to `POST /api/v1/repos/.../diffpatch` are the clearest signal.
In the logs, search for calls to the diffpatch endpoint. With the standard Linux log location:
``` grep -E '"POST /api/v1/repos/[^/]+/[^/]+/diffpatch' /var/lib/gitea/log/gitea.log ```
Successful POSTs from newly created accounts, or from accounts that normally do not touch the target repository, are solid leads. Suspicious user IDs can be enumerated with:
``` grep -E 'User created' /var/lib/gitea/log/gitea.log | head -n 50 ```
Gitea splits logs by mode, so also review `ssh.log` (for SSH access from accounts you do not recognize), `http.log` (for anomalous spikes in POST traffic to the REST API), and `xorm.log` if you have SQL-level debugging enabled. A typical compromise sequence combines three events within a few seconds: account creation, repository creation, and a call to `diffpatch`. Searching for that sequence explicitly accelerates the investigation:
``` grep -E 'User created|repo created|/diffpatch' /var/lib/gitea/log/gitea.log \ | tail -n 200 ```
At the OS level, look for unexpected child processes. The user running Gitea (by default `git`) should not be spawning shells or miner binaries. Useful commands:
``` ps -u git -o pid,ppid,start,cmd --forest ls -la /proc/*/cwd 2>/dev/null | grep -E '/tmp|/dev/shm' find / -user git -type f -newer /var/lib/gitea/data/gitea.db -not -path '/var/lib/gitea/*' 2>/dev/null ```
The Habr-documented payloads dropped files in `/tmp` and `/dev/shm`, tried to download binaries via `curl` or `wget`, and wrote a miner script into a working path inside the repository. Any appearance of `xmrig`, `minerd`, `kdevtmpfsi`, or similar names in processes owned by the `git` user is a red alert.
On the network side, review outbound connections from the Gitea host. The documented instance reached out to mining pools and to public Git repositories to pull the shell-loader. With `ss` or `tcpdump`:
``` ss -tnp 'state established' | grep -E ':(3333|4444|5555|7777|8888|14444|14433)' ```
These are typical mining pool ports. If your Gitea runs in Docker, the equivalent is `docker exec gitea ss -tnp` from the host.
A useful triage ritual is to compare the baseline outbound behavior of the Gitea container with what you see now. In a clean installation, the only outbound traffic should be optional email notifications, optional mirror pulls, and optional webhook deliveries. Anything else, especially connections to unfamiliar IP ranges on high ports, warrants investigation. Capture the live state with `ss -tnp` and the historical state from your flow logs or a netflow collector, and diff them. The Habr incident showed the attacker using `curl` and `wget` to fetch second-stage payloads from public Git repositories; if your container normally never makes outbound HTTPS to github.com or gitlab.com, those flows stand out immediately.
Upgrade procedure
For a Docker installation using the official image, pin the version to 1.27.2 or newer and force a fresh pull. Edit `docker-compose.yml` and update the image line:
``` image: gitea/gitea:1.27.2 ```
Then:
``` docker compose pull gitea docker compose up -d gitea docker compose exec gitea gitea --version ```
For a `systemd`-managed binary install, download the tarball from the official mirror, replace the binary, and restart:
``` sudo systemctl stop gitea sudo mv /usr/local/bin/gitea /usr/local/bin/gitea.bak sudo curl -L -o /usr/local/bin/gitea https://dl.gitea.com/gitea/1.27.2/gitea-1.27.2-linux-amd64 sudo chmod +x /usr/local/bin/gitea sudo systemctl start gitea gitea --version ```
Before bringing anything back up, capture a forensic snapshot of the current state. Even if you do not suspect compromise, this snapshot is your rollback point:
``` docker commit gitea gitea-pre-upgrade-2026-08-30 # or, for a native binary sudo tar czf /var/backups/gitea-data-2026-08-30.tgz /var/lib/gitea ```
If your instance was internet-facing with open registration enabled before the patch, treat it as potentially compromised. Rotate API tokens, database secrets, runner SSH keys, webhooks, and OAuth credentials. Rotation is not optional; the Habr incident documented limited persistence precisely because secrets were rotated in time.
A minimal operational checklist for that rotation includes: SSH deploy keys configured per repository (one per service), container registry access tokens if Gitea is acting as a registry, encrypted secrets stored in Gitea Actions, outgoing webhooks carrying Bearer tokens, OAuth credentials against GitHub or GitLab if you use mirroring, and the internal database password. The local admin password also belongs in this rotation if your installation uses local authentication. Auditing the Actions log is particularly important: an attacker with RCE can create a new workflow that runs automatically and executes code on your runners.
Recommended hardening
The patch closes the current vector, but Gitea's overall attack surface deserves sustained attention. Four controls make the difference.
First, disable open registration. In `app.ini`, under `[service]`, set `DISABLE_REGISTRATION = true`. If you must allow registrations, set `ALLOW_ONLY_INTERNAL_REGISTRATION = true` and require email confirmation with `ENABLE_NOTIFY_MAIL = true`. That removes the "anonymous visitor creates account and repository" path.
Second, restrict hooks. Under `[repository]`, set `DISABLE_HOOKS = true` if your workflow does not depend on Git webhooks. If you need them, set `DISABLE_GIT_HOOKS = true` so the web UI cannot install custom hooks; route hook administration through centralized configuration on the server.
Third, segment the network. Gitea does not need direct internet egress to do its job. In Docker, an internal network without egress:
``` networks: internal: internal: true ```
If you need to clone remote repositories, proxy through a dedicated runner with controlled egress. Blocking direct outbound connectivity from the Gitea container breaks most RCE-to-miner chains, because the attacker loses the download route for the payload.
Fourth, monitor exposure. Shadowserver publishes daily counts of vulnerable IPs. Note the figure and compare against your own exposure. A Gitea instance should be reachable only through your corporate VPN or an authenticated reverse proxy, never directly on port 3000 to the public internet. Configure fail2ban or equivalent against the login and registration endpoints.
The underlying lesson
CVE-2026-60004 is not an exotic bug. It is a reminder of how the trust chain breaks when a high-privilege API accepts unsanitized input from a low-privilege user. The pattern "an endpoint that materializes files at structural Git paths through an apply mode that allows path escapes" has been documented in Git security literature for years. The PoC is public, the affected version range is huge, and the default configuration multiplies the impact. If your organization runs Gitea, today is a good day to check the version, audit hooks, rotate secrets, and turn off open registration.
The deeper lesson is operational: the time between a patch landing and adversaries exploiting it is shrinking. Gitea 1.27.1 shipped on July 27. CISA added the CVE to the KEV catalog on August 25. Shadowserver counted over 8,300 exposed, unpatched instances on August 27. That is roughly a month of runway that attackers used to scan, fingerprint, and weaponize at scale. Treat every critical Gitea advisory as a 72-hour SLA rather than a quarterly maintenance item, and bake upgrade rehearsals into the change calendar so that pulling the new image does not require an unscheduled review board. Subscribe to the Gitea security RSS feed, the GHSA advisory stream for `gitea/gitea`, and the CISA KEV catalog. Self-hosted Git forges are not background infrastructure anymore. They are at the front line, and they need the operational discipline that implies.