Someone left the keys to your entire codebase hanging on the front door handle
CVE-2026-60004 is a code-injection flaw in Gitea's POST /api/v1/repos/{owner}/{repo}/diffpatch endpoint, present in every release from 1.17 through 1.27.0 — roughly four years of releases. By sending a crafted patch that triggers an add/add collision in Git's three-way merge fallback, an attacker writes a malicious file into the Git hooks directory of a temporary bare clone. The next Git operation executes that hook as the Gitea service account, giving the attacker full OS-level command execution. Because Gitea's default configuration ships with open self-registration, no email confirmation, and no admin approval, an anonymous internet user can create an account, create a repository, and fire the exploit — no prior credentials required.
The CNA-assigned CVSS 9.8 is accurate and, if anything, undersells the operational impact. Gitea is not a random web app — it is the source-code management layer for every project it hosts. RCE on the Gitea server means access to every repository, every deploy key, every CI/CD webhook secret, and every stored credential. On default installations the attack is unauthenticated and trivially scriptable with a public PoC. The only friction is that the target must be reachable and running a vulnerable version. There is no reason to downgrade this.
4 steps from start to impact.
Register or authenticate to Gitea
- Gitea instance reachable over the network
- Open registration enabled (default) OR existing write-access credentials
- Instances behind VPN or with
DISABLE_REGISTRATION = trueblock anonymous signup
/user/sign_up followed immediately by API calls — anomalous for a brand-new account.Create or access a writable repository
- Authenticated session from step 1
- Repository creation allowed (default)
- Environments that restrict repo creation to admins limit this path
Send malicious diffpatch payload
POST /api/v1/repos/{owner}/{repo}/diffpatch request containing a specially crafted patch. The patch exploits an add/add collision in Git's three-way merge, causing the patched content to land in the .git/hooks/ directory of the temporary bare clone. The hook file is made executable and contains attacker-controlled shell commands. No outbound callback is needed — the PoC stores command output in Git objects retrievable over authenticated smart HTTP.- Write access to target repository
- Gitea version 1.17–1.27.0
- Git >= 2.32 on server
- Writable and executable temp filesystem
- Read-only or
noexec-mounted temp filesystems would prevent hook execution - Git versions below 2.32 do not support the required merge behavior
.git/hooks path traversal patterns. API audit logs will show the diffpatch call.Hook executes as Gitea service account
git or gitea. This yields OS-level command execution: read all repos, exfiltrate secrets, pivot to CI/CD runners, or install persistence.- Successful hook write from step 3
- A Git operation triggers the hook (often immediate)
- Containerized Gitea with minimal capabilities and read-only root FS limits lateral movement but does not prevent initial RCE
.git/hooks/ directories.The supporting signals.
| In-the-wild status | No confirmed exploitation as of 2026-08-05. Gitea's July 28 advisory does not report active attacks. Not KEV-listed. |
|---|---|
| Proof of concept | Public. A working PoC is included in the official Gitea security advisory (July 28, 2026). Researcher Shai Rod (NightRang3r) published the discovery. The PoC requires no outbound callback — output is retrieved via Git objects over authenticated smart HTTP. |
| EPSS score | Not yet scored by FIRST (CVE too recent as of 2026-08-05). Given public PoC + default-unauthenticated attack surface, expect high-percentile EPSS once scored. |
| KEV status | Not listed on CISA KEV as of 2026-08-05. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H — 9.8 Critical. Network-reachable, low complexity, no privileges on default installs, no user interaction, full CIA impact. |
| Affected versions | Gitea 1.17 through 1.27.0 (~4 years of releases). Most self-hosted deployments run the 1.23–1.26 lines. |
| Fixed version | Gitea 1.27.1 (released July 27, 2026). Gitea Cloud instances auto-upgraded. Fix converts temp clone from bare to non-bare, preventing hook-directory writes. |
| Exposure data | ~6,200 Gitea instances indexed on Shodan (per Sysdig data). Default open-registration means many are exploitable without prior credentials. |
| Disclosure timeline | Fix merged July 26, 2026 → v1.27.1 released July 27 → advisory with PoC published July 28. The release notes did not flag the fix as security-critical, potentially delaying patching. |
| Reporter | Shai Rod (NightRang3r) — independent security researcher. GitHub profile. |
noisgate verdict.
Supply-chain pivot via source-code management server — Gitea *is* the SCM layer; RCE here gives the attacker every repository, deploy key, webhook secret, and CI/CD integration credential on the instance, making fleet-wide supply-chain compromise the expected outcome, not a theoretical edge case. The public PoC, default-unauthenticated attack surface, and four-year affected version window leave no room for a lower bucket.
Why this verdict
- Unauthenticated on default installs: Open registration is the default — the attacker self-provisions the write access the chain needs. This is functionally pre-auth RCE on unmodified deployments.
- Public weaponized PoC with zero user interaction: The advisory itself ships a working exploit. No callback infrastructure needed; output is exfiltrated via Git objects. Script-kiddie-grade barrier to entry.
- Role multiplier: Gitea is canonically an SCM/CI-adjacent component. 100% of installs serve this role by definition. RCE on Gitea = access to all hosted source code, secrets embedded in repos, deploy keys, CI/CD webhook tokens, and runner configurations. Blast radius is supply-chain-scale. This sets a CRITICAL floor that friction cannot override.
- Four-year affected window: Versions 1.17–1.27.0 span the vast majority of the installed base, including the most popular 1.23–1.26 release lines. The narrow unaffected population is pre-1.17 legacy only.
- Stealth-friendly release notes: The v1.27.1 release notes did not flag this as a security fix, meaning many operators may not have prioritized the upgrade — widening the real-world exposure window.
Why not higher?
This is already assessed at the maximum actionable severity (CRITICAL / 9.8). There is no higher bucket.
Why not lower?
Every downgrade factor was evaluated and rejected. The only friction is network reachability and registration policy — but default config is open, and ~6,200 instances are internet-facing per Shodan. Gitea is a canonical high-value SCM component where RCE yields supply-chain compromise by definition. The public PoC makes exploitation trivial. There is no credible basis for reducing below CRITICAL.
What to do — in priority order.
- Disable public registration immediately — Set
DISABLE_REGISTRATION = truein[service]ofapp.iniand restart Gitea. This removes the self-provisioning path for unauthenticated attackers. Does not protect against existing authenticated users with write access. Deploy within the noisgate mitigation SLA of ≤3 days. - Restrict or disable the diffpatch API endpoint — Use a reverse-proxy rule (nginx, Caddy, Traefik) to block
POSTrequests matching/api/v1/repos/*/diffpatch. This surgically kills the vulnerable code path without disrupting normal Git operations. Deploy within 3 days per noisgate mitigation SLA. - Mount Gitea temp directories with noexec — If the Gitea process's temp filesystem is mounted
noexec, planted hooks cannot execute even if written. Addnoexecto the mount options for/tmpor the configuredTEMP_PATH. Verify withmount | grep tmp. Deploy within 3 days. - Run Gitea in a hardened container with read-only root filesystem — Use
--read-onlyand drop all unnecessary Linux capabilities. This limits post-exploitation lateral movement even if the hook fires. Not a substitute for patching but reduces blast radius. - Audit existing repository write permissions — Review all users with write or admin access to repositories. Remove stale accounts and enforce MFA. This reduces the insider-threat variant of the attack path.
- Network-level IP allowlisting alone — does not help if the attacker is an authenticated internal user or if the Gitea instance is intentionally public-facing for open-source collaboration.
- Git commit signing enforcement — the attack uses the diffpatch API, not a normal Git push. Commit signature verification does not inspect or block API-applied patches.
- Rate limiting on the API — the exploit requires a single HTTP request. Rate limiting has no meaningful effect on a one-shot RCE.
Crowdsourced verification payload.
Run this script on the Gitea server (or any host with SSH/API access to it) as any user who can read the Gitea binary or query its API. Example: bash check_cve_2026_60004.sh https://gitea.example.com — no special privileges required beyond reading the /api/v1/version endpoint.
#!/usr/bin/env bash
# check_cve_2026_60004.sh — Detect Gitea versions vulnerable to CVE-2026-60004
# Usage: bash check_cve_2026_60004.sh <GITEA_BASE_URL>
# Exit codes: 1=VULNERABLE, 0=PATCHED, 2=UNKNOWN
set -euo pipefail
if [[ $# -lt 1 ]]; then
echo "Usage: $0 <GITEA_BASE_URL>"
echo "Example: $0 https://gitea.example.com"
exit 2
fi
BASE_URL="${1%/}"
# Fetch version from public API endpoint
VERSION_JSON=$(curl -sf --max-time 10 "${BASE_URL}/api/v1/version" 2>/dev/null) || {
echo "UNKNOWN — could not reach ${BASE_URL}/api/v1/version"
exit 2
}
VERSION=$(echo "$VERSION_JSON" | grep -oP '"version"\s*:\s*"\K[^"]+' 2>/dev/null) || {
echo "UNKNOWN — could not parse version from API response"
exit 2
}
echo "Detected Gitea version: ${VERSION}"
# Extract major.minor.patch
IFS='.' read -r MAJOR MINOR PATCH <<< "$(echo "$VERSION" | grep -oP '^[0-9]+\.[0-9]+\.[0-9]+')"
if [[ -z "$MAJOR" || -z "$MINOR" || -z "$PATCH" ]]; then
echo "UNKNOWN — could not parse semantic version from '${VERSION}'"
exit 2
fi
# Vulnerable: 1.17.0 <= version < 1.27.1
if (( MAJOR == 1 )); then
if (( MINOR < 17 )); then
echo "PATCHED — version ${VERSION} predates the vulnerable code (introduced in 1.17)"
exit 0
elif (( MINOR > 27 )); then
echo "PATCHED — version ${VERSION} is newer than the fixed release"
exit 0
elif (( MINOR == 27 && PATCH >= 1 )); then
echo "PATCHED — version ${VERSION} includes the fix (1.27.1+)"
exit 0
else
echo "VULNERABLE — version ${VERSION} is in the affected range (1.17.0–1.27.0)"
exit 1
fi
elif (( MAJOR > 1 )); then
echo "PATCHED — major version ${MAJOR} is beyond affected range"
exit 0
else
echo "PATCHED — version ${VERSION} predates affected range"
exit 0
fiIf you remember one thing.
DISABLE_REGISTRATION = true) and block the /api/v1/repos/*/diffpatch endpoint at your reverse proxy by end of day Wednesday. Under the noisgate remediation SLA (≤90 days for CRITICAL), complete the upgrade to 1.27.1 across all instances — but given the trivial exploitability and public PoC, treat the remediation as a same-week emergency, not a 90-day project. Audit your Gitea instances for signs of compromise: check .git/hooks/ directories for unexpected files, review access logs for diffpatch API calls, and inspect for new user registrations since July 28. If you find any Gitea instance exposed to the internet with open registration, assume it may already be compromised and begin incident response.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.