Jan 24, 2026·6 min read·33 visits
If you kick a user out of a private Gitea repository, they shouldn't see what happens next. Due to a failure to check permissions during notification retrieval, removed users could still see the titles of new Issues and Pull Requests via the API. Fixed in version 1.25.4.
A classic logic flaw in Gitea's notification system allows users to view metadata of private repositories they no longer have access to. By failing to re-validate permissions at the time of API retrieval, Gitea effectively allowed 'zombie' access to sensitive issue and PR titles.
Imagine this scenario: You have a contractor working on a sensitive project. Let's call him Bob. Bob is sloppy. You decide to revoke Bob's access to the Project-Manhattan repository. You go into Gitea settings, click 'Remove Collaborator', and breathe a sigh of relief. The locks are changed. Bob is gone.
Or is he?
In the world of CVE-2026-20800, Bob might be locked out of the front door, but Gitea left the bathroom window wide open. While Bob can no longer browse the code or push commits, the Notification API (/api/v1/notifications) acts like a gossiping neighbor. It happily continues to serve up details about what's happening inside the repository he was just kicked out of.
This isn't a complex memory corruption bug or a fancy buffer overflow. It's a fundamental misunderstanding of the difference between past access and current access. It’s a logic flaw that turns the useful feature of "staying updated" into a persistent surveillance tool for disgruntled ex-collaborators.
The vulnerability lies deep within services/convert/notification.go. This is the part of the Gitea codebase responsible for taking a raw notification record from the database and dressing it up into a pretty JSON object for the API response.
When a user queries their notifications, the system retrieves a list of events (like "New Issue Created" or "PR Merged"). The fatal mistake was an assumption of static trust. The code assumed that if a notification record existed in the database for a specific user, that user must have permission to see the associated repository data.
Instead of performing a fresh authorization check against the current access control list (ACL), the code simply hardcoded a AccessModeRead permission into the response object. It was effectively saying, "If you have a notification, you have read access." This is a classic Time-of-Check to Time-of-Use (TOCTOU) logical fallacy, but strictly in the business logic layer. The permission was valid when the notification was created, but the system failed to verify if it was still valid when the notification was read.
Let's look at the vulnerable code in ToNotificationThread. This function constructs the API response. Notice how it handles the repository object:
// VULNERABLE CODE (Pre-1.25.4)
if n.Repository != nil {
// The fatal flaw: Hardcoding 'AccessModeRead'
result.Repository = ToRepo(ctx, n.Repository, access_model.Permission{AccessMode: perm.AccessModeRead})
// ... code continues to populate Subject (Issue/PR Title) ...
}The function ToRepo is called with a manually constructed Permission struct. The developers explicitly told the converter: "Treat this user as if they have Read access." There is no database query here checking if the user is actually a collaborator or if the repo is still public.
Now, look at the fix introduced in version 1.25.4 via PR #36339. The patch forces a reality check:
// PATCHED CODE (1.25.4)
if n.Repository != nil {
// The fix: ASK the database for current permissions
perm, err := access_model.GetUserRepoPermission(ctx, n.Repository, n.User)
if err != nil {
log.Error("GetUserRepoPermission failed: %v", err)
return result
}
// Only show repo details if they actually have access RIGHT NOW
if perm.HasAnyUnitAccessOrPublicAccess() {
result.Repository = ToRepo(ctx, n.Repository, perm)
}
}The difference is night and day. The patched code stops assuming and starts verifying via access_model.GetUserRepoPermission.
Exploiting this requires no special tools—just a web browser or curl. Here is the attack chain for a user whose access has been revoked:
Super-Secret-App).curl -H "Authorization: token <user_a_token>" \
https://gitea.example.com/api/v1/notifications?status-types=unreadsubject object:{
"id": 1337,
"repository": {
"full_name": "org/Super-Secret-App",
"private": true
},
"subject": {
"title": "CRITICAL: Hardcoded AWS Keys in Payment Module",
"type": "Issue"
}
}User A now knows about the critical security flaw in the payment module, simply by reading the title leaked in the notification stream.
You might argue, "So what? They can't see the code." While true, in the world of software development, titles are metadata, and metadata is data.
Knowing the titles of Issues and Pull Requests can reveal:
This vulnerability breaks confidentiality. It violates the principle of least privilege by allowing data to persist beyond the authorization boundary.
The remediation is straightforward: Upgrade to Gitea 1.25.4. The patch ensures that every time a notification is serialized for the API, a fresh permission check is performed against the current state of the database.
If you cannot upgrade immediately, there are no clean configuration workarounds other than manually purging the notification database table for users who have been offboarded, which is a risky database operation. The only real fix is the code change.
For security teams, this serves as a reminder: Access Control Lists are not static. Just because a user was allowed to receive a notification yesterday doesn't mean they are allowed to read it today. Always validate permissions at the time of access (Time-of-Use), not just at the time of creation.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Gitea Gitea | <= 1.25.3 | 1.25.4 |
| Attribute | Detail |
|---|---|
| CWE | CWE-862 (Missing Authorization) |
| Attack Vector | Network (API) |
| CVSS v3.1 | 6.5 (Medium) |
| Impact | Information Disclosure |
| Privileges | Low (Authenticated User) |
| User Interaction | None |
An issue was discovered in Go's `golang.org/x/crypto/ssh/knownhosts` package where a revoked Certification Authority (CA) public key was not correctly checked for revocation during SSH host certificate validation. This allowed clients or servers utilizing the library to validate and trust host certificates issued by explicitly revoked CAs.
An authorization bypass vulnerability exists in the golang.org/x/crypto/ssh package prior to version 0.52.0. When an SSH server is configured with a custom VerifiedPublicKeyCallback that returns a Permissions object containing a source-address critical option, the server fails to validate and enforce the restriction. This allows remote clients with valid public keys to bypass IP-based access restrictions and authenticate from unauthorized network locations.
A critical vulnerability exists in MessagePack-CSharp's typeless deserialization mechanism where configured blocklists fail to recursively inspect nested types. An attacker can bypass security restrictions by wrapping unauthorized types in arrays or generic collections, allowing insecure deserialization and remote code execution.
A critical prototype pollution vulnerability exists in the i18next-fs-backend Node.js package (prior to version 2.6.6) through its translation persistence layer. When handling missing translation keys, insecure traversal of JSON objects via the getLastOfPath function allows remote, unauthenticated attackers to mutate Object.prototype, potentially leading to denial of service, security bypasses, or remote code execution.
CVE-2026-48708 details a critical concurrency synchronization flaw in OliveTin versions < 3000.13.0. A shared package-level text/template.Template instance is accessed concurrently across multiple goroutines without proper synchronization. When concurrent request processing occurs, a race condition causes Go runtime panics or command contamination across separate sessions, enabling denial of service or execution of contaminated commands.
A missing authorization vulnerability in the OliveTin system allows unauthenticated remote actors to query the ValidateArgumentType RPC endpoint. By exploiting this flaw, attackers can execute systematic brute-force and side-channel validation attacks to enumerate active action binding IDs, parameter structures, and operational metadata, bypassing configured guest authentication barriers.