Jan 24, 2026·6 min read·51 visits
In Gitea versions up to 1.25.3, the 'Scheduled Auto-Merge' feature lacked proper authorization checks on its cancellation endpoints. This means any user with read access (even a lowly intern or a random public user) could cancel a maintainer's scheduled merge, causing silent delays in deployment. Fixed in v1.25.4 by enforcing strict permission checks.
A logic flaw in Gitea's access control allows any user with read access to a repository to cancel scheduled auto-merges, effectively enabling low-privileged users to disrupt CI/CD workflows and release pipelines.
Automation is the holy grail of DevOps. We set up CI/CD pipelines, branch protections, and auto-merges so we can go grab coffee while the robots do the heavy lifting. Gitea, a popular lightweight Git server, offers a 'Scheduled Auto-Merge' feature. It promises that once your tests pass and approvals land, the code merges itself. It's 'set it and forget it.'
But here's the kicker with CVE-2026-20888: 'forgetting it' was dangerous. Because while you were sipping that coffee, expecting your code to be in production, someone else—potentially a user with barely any privileges—could simply reach over and turn off the switch.
This isn't a complex memory corruption bug or a fancy SQL injection. It's a classic logic flaw where the application simply forgot to ask, 'Hey, are you actually allowed to touch this?' It’s the digital equivalent of locking your front door but installing a 'Cancel Security' button on the outside wall.
The vulnerability resides in how Gitea handled the cancellation of these scheduled merges. There are two ways to tell Gitea to stop an auto-merge: via the Web Interface and via the API.
In the Web Interface (routers/web/repo/pull.go), the handler for canceling the auto-merge explicitly lacked an authorization check. It retrieved the Pull Request data, but it didn't verify if the person clicking the button was the original scheduler ('the doer') or a repository maintainer. If you could see the PR, you could likely hit the endpoint.
The API Endpoint (routers/api/v1/repo/pull.go) was slightly more confused. It did have a check, but it was the wrong one. It checked if the user was a Repo Admin. This is a classic 'Goldilocks' failure in permissions:
The result? A permission mismatch where the barriers to entry were either non-existent or nonsensical.
Let's look at the smoking gun. The fix involved unifying the logic to use a proper permission check function: pull_service.IsUserAllowedToMerge.
In the API Router, notice how the check shifts from a blunt 'Is Admin' check to a nuanced 'Can Merge' check:
// routers/api/v1/repo/pull.go
// BEFORE: Only Admins allowed? Weird logic.
- allowed, err := access_model.IsUserRepoAdmin(ctx, ctx.Repo.Repository, ctx.Doer)
// AFTER: Check if the user actually has merge rights.
+ allowed, err := pull_service.IsUserAllowedToMerge(ctx, pull, ctx.Repo.Permission, ctx.Doer)However, the real horror show was in the Web Router, where the check was effectively absent or implicit. The patch forces a verification step:
// routers/web/repo/pull.go
// ADDED IN FIX:
if ctx.Doer.ID != scheduledPR.DoerID {
// If you aren't the one who scheduled it...
allowed, err := pull_service.IsUserAllowedToMerge(ctx, ctx.Repo.Repository, ctx.Repo.Permission, ctx.Doer)
if !allowed {
ctx.Error(403)
return
}
}This code block says: 'If you are the person who scheduled this, proceed. If not, prove you have merge rights.' Simple, effective, and previously missing.
Exploiting this doesn't require Metasploit. It requires curl and a grudge.
Imagine a scenario where a developer, Alice, schedules a critical bug fix to auto-merge over the weekend once the slow test suite finishes. Bob, a disgruntled user with read-only access to the public repo, decides to cause chaos.
The Attack Chain:
42) and the repo owner/name.# The Web Interface vector
curl -X POST "https://gitea.target.local/owner/repo/pulls/42/cancel_auto_merge" \
-H "Cookie: i_like_gitea=..." \
-H "X-Csrf-Token: ..."scheduled_auto_merge table entry is deleted. Alice's PR passes tests on Sunday morning, but Gitea does nothing. The fix is not deployed. Monday morning starts with an incident call.Because the impact is 'Integrity Low' and 'Confidentiality None', this flies under the radar of many scanners. But for a DevOps team, this is a denial-of-service against their workflow.
Why panic over a CVSS 4.3?
Because reliability is security. In modern DevSecOps, we treat infrastructure as code and deployments as automated pipelines. If an attacker can silently decouple the train from the engine, the train stops moving, and nobody notices until the passengers start screaming.
While this won't dump your database, it allows an insider threat or a public nuisance to sandbag your engineering velocity with zero technical sophistication.
The mitigation is straightforward: Update to Gitea v1.25.4.
If you cannot update immediately, you are in a tight spot. Since this logic resides in the compiled Go binary, you can't just 'patch a file' like in PHP or Python. You would need to:
POST requests to */cancel_auto_merge for non-admin IP addresses, but mapping permissions to IPs is a nightmare.automerge.RemoveScheduledAutoMerge action in your audit logs. If you see it triggered by someone who isn't a maintainer, you have a problem.But seriously, just update the binary.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Gitea Gitea | <= 1.25.3 | 1.25.4 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-862 |
| Attack Vector | Network |
| CVSS | 4.3 (Medium) |
| Impact | Integrity (Low) |
| Privileges | Low (Read Access) |
| Exploit Status | PoC Available |
The product does not perform an authorization check when an actor attempts to access a resource or perform an action.
A remote code execution vulnerability exists in Microsoft .NET due to untrusted data deserialization within the PresentationCore component. The flaw stems from implicit auto-conversion flags during clipboard data retrieval operations.
An out-of-bounds write vulnerability in Microsoft .NET Framework, .NET runtime, and Visual Studio allows unauthenticated remote attackers to execute arbitrary code via crafted pixel-shader bytecode or auto-converted clipboard payloads.
ASP.NET Core contains a thread deadlock vulnerability (CWE-833) that allows an unauthenticated remote attacker to cause a denial of service over the network by exhausting server thread pool resources.
CVE-2026-70035 is a high-severity Remote Code Execution (RCE) vulnerability in Microsoft .NET Framework, .NET runtime, and Visual Studio caused by improper handling during the deserialization of untrusted data (CWE-502). An unauthenticated attacker can execute arbitrary code within the context of the affected process by delivering a crafted serialized payload.
An elevation of privilege vulnerability exists in ASP.NET Core and Microsoft Visual Studio due to improper synchronization during concurrent thread execution. Authenticated remote attackers with low privileges can exploit a timing window to manipulate shared resources and assume higher-level execution contexts.
A path traversal vulnerability (CWE-22) in Contao CMS allows unauthenticated remote attackers to bypass directory boundary restrictions in ImagesController and access files within the project directory.