Abstract duplicate patches entering a bare repository and activating a hidden Git hook for Gitea CVE-2026-60004

Two Patches, One Hook: Inside Gitea CVE-2026-60004

The second POST /diffpatch is the request that changes the meaning of the sequence. A new account appears, creates a repository, submits the same patch twice, then touches Git over smart HTTP. Each event looks ordinary in isolation. Together they describe a path from a low-privilege repository writer to shell execution as the Gitea service account.

This is a composite defensive scenario based on the vendor advisory and a public operator field report, not a claim about a system observed by this publication. The useful detail is the attacker’s decision point: the first patch prepares an add/add collision; the second forces Git into a three-way fallback. The repository content is no longer just content.

The problem is not merely a patch endpoint

Gitea’s advisory describes CVE-2026-60004 as remote code execution through Git hook installation. An ordinary user with write access to any repository can reach the vulnerable path. Where open registration is enabled, an unauthenticated visitor can create that privilege for themselves. The affected range is Gitea 1.17 through 1.27.0; 1.27.1 contains the fix.[1]

CISA added the vulnerability to its Known Exploited Vulnerabilities catalog on August 25, 2026, with a remediation deadline of August 28 for US federal civilian agencies.[4] That changes the operational question. Internet-exposed instances should not treat this as a normal patch-window item, and responders should not assume an upgrade erases evidence of earlier execution.

The exposed trust boundary resembles the one in the TeamCity deserialization chain, but the primitive is different. Here, the application delegates repository mutation to Git. The dangerous behavior emerges from the interaction between Gitea’s temporary bare clone, Git’s merge fallback, filesystem layout, and hook execution. No single API parameter says “run this command.”

Why --cached did not mean “no checkout”

The vulnerable code invoked git apply --index --recount --cached --binary and added -3 on Git 2.32 or newer. The intuitive reading of --cached is that Git should update only the index. The three-way fallback complicates that assumption. Submitting the same add operation twice creates an add/add collision, and Git may check out the indexed path while resolving it.[1]

In a normal working clone, a path named hooks/post-index-change would sit beneath the worktree. In a bare clone, the repository root is $GIT_DIR. The same relative path therefore lands in the active hooks directory. If the patch preserves an executable mode, an index-writing operation can invoke the installed hook. The advisory also notes that the hook’s return value is not propagated to the diffpatch response, so a failed-looking request is not reliable evidence that command execution failed.[1]

The patch in Gitea 1.27.1 refactors the Git patch-apply path and adds coverage around the affected behavior. The release notes list “Fix: git patch apply” under security changes, while the associated commit modifies services/repository/files/patch.go and introduces a dedicated patch test.[2][3] This is a useful review lesson: a security fix can look like a small correctness refactor when examined without the surrounding exploit mechanics.

Attack chain: ordinary writer to service account

Five-stage attack flow from Gitea account creation and duplicate diffpatch requests to Git hook execution, service shell access, and secrets exposure
CVE-2026-60004 attack flow: repository write access becomes an executable Git hook inside a bare temporary clone.

The practical chain is short:

  1. Acquire repository write access. Open registration makes this a no-prior-credentials path, but disabling registration only removes that entry route. A compromised developer account or low-trust collaborator still satisfies the authorization requirement.
  2. Create or select a writable repository. Private visibility does not constrain server-side execution. It only changes who can see the evidence.
  3. Submit the crafted patch twice. The duplicate operation drives Git into the collision and three-way behavior described by the advisory.
  4. Install and trigger post-index-change. The hook executes as the Gitea OS user during an index operation. A public Metasploit development pull request shows the same diffpatch and hook primitive affecting Forgejo branches derived from the same code lineage.[5]
  5. Expand impact from the service context. The Gitea user commonly has access to repositories, app.ini, database credentials, OAuth material, webhooks, Actions secrets, and internal network paths. Root is not inherent to the CVE. Host-level compromise depends on local privilege, container mounts, socket exposure, sudo rules, or a second vulnerability.

A public field report documents a sequence of sign-up, repository creation, two diffpatch calls, a Git HTTP operation, service-account execution, and later privilege escalation through a deployment-specific passwordless sudo rule.[6] Treat that report as incident evidence from one operator, not a universal payload profile. The durable detection value is the ordered behavior, not the reported username, IP address, or miner hash.

A safe mechanism lab

The following isolated lab was executed during preparation. It does not call Gitea, send a malicious patch, open a listener, or reproduce the network exploit. It demonstrates only the security-relevant invariant: an executable hook under a bare repository’s $GIT_DIR/hooks runs during an index write.

lab=$(mktemp -d)
git init --bare "$lab/repo.git"

cat > "$lab/repo.git/hooks/post-index-change" <<'HOOK'
#!/bin/sh
printf 'hook-fired uid=%s\n' "$(id -u)" >> "$GIT_DIR/hook-fired.log"
HOOK
chmod 700 "$lab/repo.git/hooks/post-index-change"

GIT_DIR="$lab/repo.git" git read-tree --empty
cat "$lab/repo.git/hook-fired.log"
rm -rf "$lab"

The observed marker was hook-fired uid=1000, and the temporary directory was removed. This lab is safe precisely because it starts after the vulnerable application logic. It tests the consequence defenders must model without publishing another copy-and-paste route exploit.

Detection: hunt for the sequence, then the boundary crossing

Start with HTTP history. A tight sequence of account creation, repository creation, repeated /diffpatch requests against the same repository, and immediate Git smart-HTTP traffic is higher signal than any one endpoint. Preserve response codes and timing, but do not filter out 500 responses. The hook can execute even when the API response suggests an error.

LayerHigh-value signalDefensive action
Reverse proxyTwo near-identical diffpatch calls after account and repo creationCorrelate by account, repository, source, and a short time window
FilesystemExecutable files created under temporary or repository hooks/ pathsAlert on create, chmod, and rename events; retain the file before deletion
Processgitea spawning git, followed by a shell, downloader, interpreter, or discovery utilityCapture ancestry, cwd, environment metadata, and network connections
IdentityNew low-reputation account immediately exercising repository APIsRisk-score behavior rather than relying on username patterns
EgressGitea service context initiating novel outbound DNS, HTTP, or raw TCPDefault-deny where practical; allowlist required integrations

File monitoring must cover temporary clones, not only persistent repositories. The vulnerable hook can exist briefly under Gitea’s temporary data path and disappear after the operation. On Linux, auditd, fanotify-based EDR, or eBPF telemetry should capture executable creation and process ancestry in those directories. A generic “shell spawned by web server” rule may miss the chain because the parentage can include one or more Git processes.

Also review what the service account could read. The post-exploitation problem is similar to the secret-boundary failures discussed in GitLost’s repository exposure chain: the initial primitive matters less once a trusted automation identity can reach source, tokens, and internal services. Rotate credentials based on verified exposure paths, not merely on whether a miner was found.

Containment and hardening

Upgrade Gitea to 1.27.1 or later. If immediate patching is impossible, remove public exposure to the affected service, restrict repository creation and write access, and disable open registration. Those are temporary risk reductions, not substitutes for the fixed code. The advisory lists Git 2.32 or newer, an enabled diffpatch route, and writable executable temporary storage among the exploit prerequisites.[1]

After suspected exploitation, isolate the instance before restarting or cleaning it. Preserve Gitea logs, reverse-proxy logs, temporary filesystem evidence, repository hooks, process telemetry, service-unit configuration, container mounts, database audit history, and cloud control-plane logs. Rebuild from a trusted image when service-account execution is confirmed. Validate repositories and Actions configuration before importing them into the rebuilt environment.

Reduce the blast radius: run Gitea as a dedicated unprivileged identity, remove broad sudo rules, avoid mounting Docker or container-runtime sockets, keep secrets out of long-lived environment variables, scope database permissions, and constrain outbound traffic. A noexec temporary mount can disrupt this specific hook condition, but test it against upgrades, package operations, and repository workflows. It is a compensating control, not proof that every code-execution path is closed.

Finally, feed the finding back into asset and exposure management. An SBOM can tell you that Gitea exists, but not whether registration is open, diffpatch is reachable, or the service identity can read production secrets. That is why the decision model in the SBOM and VEX pipeline article needs runtime context.

Strategic takeaways

CVE-2026-60004 is a delegation failure. Gitea treated Git as a safe repository transformer, while Git treated a path under $GIT_DIR/hooks as executable policy. The exploit lives in the gap between those assumptions.

For offensive reviews, map every place an application invokes a powerful interpreter, package manager, VCS, document converter, or build tool with attacker-controlled files. Test fallback paths, duplicate operations, error handling, temporary clone layouts, file modes, and cleanup. Happy-path validation rarely reaches the dangerous state transition.

For defenders, prioritize the chain that crosses a boundary: user registration to repository mutation, repository mutation to hook creation, hook execution to service shell, and service shell to secrets or host privilege. That model survives payload changes and remains useful after the easy IOCs expire.

Sources

💜 Enjoyed this content? Support the blog with USDT (TRC20):

TX7obcjHQbDUXb4mGqoASEu1QFTKT2CFGG

View support page

Paulo Rigonato

Security Engineer | Red Team | Pentest

Offensive security specialist with experience in assessments, pentesting, and Red Team operations. He works in enterprise cybersecurity and continues to share knowledge through this blog.

Certifications: OSCP | eWPTXv2 | ITILv4

💻 GitHub 🔗 LinkedIn

Similar Posts