#Gitea#Security#CVE#Self-Hosted#DevSecOps

CVE-2026-60004: Patching Gitea's Critical RCE and Hardening Self-Hosted Git Infra

webhani·

If your team runs a self-hosted Gitea instance, stop and check its version now. CVE-2026-60004 is a CVSS 9.8 remote code execution vulnerability affecting Gitea 1.17 through versions before 1.27.1, it is being actively exploited as of late August 2026, and CISA has added it to the Known Exploited Vulnerabilities catalog with a patch deadline of August 28 for US federal agencies. If you host your own Git server, that deadline is a reasonable one to adopt yourself.

What the vulnerability actually does

The flaw lives in Gitea's diffpatch API — the endpoint that applies a user-supplied patch inside a temporary Git repository, typically used for features like "edit this file and create a PR" from the web UI. A crafted patch that intentionally duplicates itself can trigger an add/add collision. Git resolves that collision by falling back to a three-way merge, and a specially built patch can steer the content of that merge into a file location that Git treats as a hook script — specifically the post-index-change hook.

That hook is not something Gitea explicitly writes; it's material that ends up on disk as an executable file, in the right place with the right name, purely as a side effect of how the merge resolves the collision. Once it exists, any subsequent Git index operation on that repository triggers it, and it runs as a normal shell script under the OS account that runs Gitea. No file upload feature, no plugin system, no admin panel required — just a patch through a legitimate API that unpatched Gitea versions apply to disk unsafely.

This is why it's rated CWE-94 (Improper Control of Generation of Code): the vulnerability is in how untrusted patch content is allowed to influence code that gets executed by the underlying Git tooling, not in some obscure edge feature.

Why "requires repo write access" doesn't mean "requires trust"

The prerequisite for exploitation is write access to a repository — you need to be able to push a patch. Under a threat model where your Gitea instance only allows access to vetted internal engineers, that sounds contained. But most Gitea instances, including a large share of default installs used by small dev shops and consultancies, ship with open self-registration enabled (DISABLE_REGISTRATION = false under [service]). An anonymous visitor registers an account, creates a repository they now own, and has write access to it — no invitation, no approval, nothing.

At that point the "requires write access" caveat evaporates. It's a remote, unauthenticated RCE against any internet-facing Gitea instance that hasn't disabled self-registration, wrapped in a technicality about needing to write to some repo, which the attacker can trivially create for themselves.

Step 1: Check your version and exposure

SSH into the host or exec into the container and check the running version:

# Docker deployment
docker exec -it gitea gitea --version
 
# Binary/systemd deployment
gitea --version
 
# Or just hit the API
curl -s https://git.yourcompany.com/api/v1/version

If the reported version is 1.17.0 or later and earlier than 1.27.1, you are exposed. Also check registration settings while you're in there:

grep -A2 "\[service\]" /path/to/gitea/conf/app.ini | grep DISABLE_REGISTRATION

If that line is missing or set to false, treat this as urgent regardless of your internal-only assumptions — assumptions about who can reach the instance are exactly what gets invalidated by a misconfigured reverse proxy or an exposed port nobody remembered about.

Step 2: Upgrade

Docker Compose is the most common deployment pattern for small teams, so start there:

# Back up first — data volume and the app.ini config
docker exec gitea gitea dump -c /data/gitea/conf/app.ini
docker cp gitea:/var/gitea-dump-*.zip ./backups/
 
# Pull the fixed version
docker compose pull gitea   # ensure image tag in compose.yml is >=1.27.1, or "latest"
docker compose up -d gitea
 
# Confirm the running version
docker exec -it gitea gitea --version

If your compose.yml pins an explicit tag (e.g. gitea/gitea:1.24), bump it to 1.27.1 or later before pulling — docker compose pull won't move you across a pinned tag on its own.

Binary or package installs: stop the service, replace the binary with the 1.27.1+ release from Gitea's official release page (verify the checksum), and restart:

sudo systemctl stop gitea
sudo cp gitea-1.27.1-linux-amd64 /usr/local/bin/gitea
sudo chown gitea:gitea /usr/local/bin/gitea
sudo chmod +x /usr/local/bin/gitea
sudo systemctl start gitea
sudo systemctl status gitea

Always run through Gitea's version-specific upgrade notes for anything between your current version and 1.27.1 — schema migrations sometimes require intermediate steps.

Step 3: If you can't patch immediately

Patching should happen within hours, not weeks, given active exploitation. If there's a legitimate delay (change control windows, testing requirements), apply these mitigations in the meantime:

Disable open self-registration — this alone removes the "unauthenticated" part of the threat model:

[service]
DISABLE_REGISTRATION = true

Restrict who can create and write to repositories. If registration must stay open for a specific reason, at minimum disable self-service repo creation and require admin approval:

[repository]
DISABLE_HTTP_GIT = false
DEFAULT_PRIVATE = private
 
[service]
REQUIRE_SIGNIN_VIEW = true

Restrict network access at the edge. Put Gitea behind a reverse proxy or VPN and allowlist source IPs if your team works from known ranges:

# nginx example — restrict to office/VPN CIDR
location / {
    allow 203.0.113.0/24;
    allow 198.51.100.0/24;
    deny all;
    proxy_pass http://127.0.0.1:3000;
}

Or at the firewall level with ufw:

sudo ufw allow from 203.0.113.0/24 to any port 3000
sudo ufw deny 3000

None of these mitigations are a substitute for the patch — they reduce the attack surface while you schedule the actual upgrade.

Step 4: Check for signs of compromise

Given active exploitation in the wild, check for compromise even if you're patching promptly — you may have been exposed before today.

Look for unexpected hook files. Every repository has a hooks/ directory under its Git data path. Scan for anything unusual, particularly post-index-change:

find /data/gitea-repositories -type f -name "post-index-change" -exec ls -la {} \;
find /data/gitea-repositories -path "*/hooks/*" -newer /path/to/known-good-timestamp -type f

Compare hook file contents against what Gitea normally generates — legitimate hooks are template-based and predictable; anything with base64-encoded payloads, curl/wget calls, or obfuscated shell is a red flag.

Check running processes and cron for miner-style indicators — high sustained CPU from unfamiliar binaries, processes running from /tmp, /dev/shm, or the Gitea data directory, and unexpected crontab entries:

ps aux --sort=-%cpu | head -20
crontab -l -u git   # or whatever user runs Gitea
find /tmp /dev/shm -type f -mtime -14 -ls

Review recent repository and patch activity. Gitea logs API access; check for diffpatch endpoint hits and unfamiliar accounts created around the same time:

grep -i "diffpatch\|/api/v1/repos.*patch" /var/log/gitea/gitea.log | tail -100
grep -i "user.*register\|SignUp" /var/log/gitea/gitea.log | tail -50

If you find evidence of compromise, don't just delete the hook and move on — rotate any credentials the Gitea service account had access to (SSH deploy keys, CI tokens, cloud credentials referenced in webhooks), and treat the host as potentially having broader persistence.

The broader lesson

Developer infrastructure — Git servers, CI runners, artifact registries — routinely runs with less operational rigor than production application servers, despite often having comparable or greater blast radius. A compromised Git server can inject malicious code into every downstream deployment it feeds; a compromised CI runner has credentials to your cloud infrastructure. CVE-2026-60004 is a clean example: a default configuration choice (open registration) turned an "authenticated" vulnerability into an unauthenticated one, and the service account running Gitea likely had SSH keys, webhook secrets, and CI integration tokens sitting right there for a cryptominer payload to find.

At webhani, when we advise clients running self-hosted Gitea or similar Git infrastructure, the standing recommendations are simple and unglamorous: subscribe to security advisories for whatever you self-host and patch on a defined cadence rather than reactively; treat self-registration and repository creation permissions as access control decisions, not defaults to leave alone; and put self-hosted dev tooling behind the same network restrictions you'd apply to a production admin panel. Developer infrastructure is production infrastructure — it deserves the same discipline.