CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-20883

The Zombie Stopwatch: Haunting Gitea with Revoked Access

Amit Schendel
Amit Schendel
Senior Security Researcher

Jan 24, 2026·6 min read·53 visits

Executive Summary (TL;DR)

Gitea forgot to check if you still work there before showing you your timesheets. If a user started a stopwatch on a private issue and then lost access to the repository, the API would still happily serve up the issue title and repo name in the stopwatch list. It's a classic Broken Access Control (CWE-284) fixed in version 1.25.4.

A logic flaw in Gitea's stopwatch feature created a persistence vulnerability where users maintained visibility into private issue metadata after access revocation. By failing to re-validate permissions during API object serialization, the system allowed 'zombie' stopwatch records to leak sensitive titles and repository names.

The Hook: The Ghost in the Machine

In the world of access control, we usually worry about the front door. Can the attacker pick the lock? Can they steal a key? But sometimes, the vulnerability isn't about getting in; it's about what you leave behind when you're kicked out. Enter Gitea's Stopwatch feature—a seemingly innocent productivity tool that allows developers to track time on issues.

Imagine this: You are a contractor hired to work on a top-secret project. You start a timer on an issue titled "Critical Security Flaw in Authentication". A week later, you are fired. Your access to the repository is revoked. You can't see the code, you can't see the issues, and you definitely shouldn't know what the team is working on.

But because Gitea's developers treated the stopwatch as a personal metric rather than a repository-tied asset, that timer keeps ticking in your personal dashboard. And unlike a real physical stopwatch, this digital ghost carries the metadata of the thing it was timing. It's not just counting seconds; it's whispering secrets from a room you're no longer allowed to enter.

The Flaw: Trusting the Past

The root cause here is a classic failure in the Object-Relational Mapping (ORM) to API conversion layer. When you build an API, you often pull raw rows from a database and pass them through a "serializer" or "converter" to turn them into pretty JSON for the frontend. In Gitea, the function responsible for this was ToStopWatches in services/convert/issue.go.

The logic was dangerously simple: "Fetch all stopwatch records associated with User X." The code assumed that if a record existed in the database, the user must have permission to see it. It treated the database state as the source of truth for authorization, rather than checking the current access control list (ACL) at runtime.

This is a Time-of-Check to Time-of-Use (TOCTOU) variant effectively turned into a permanent state. Validating permission only when the stopwatch is started is insufficient. Permissions change. Teams change. Repositories go from public to private. By failing to re-validate the relationship between the user and the repository during the read operation (the API call), the code created a permanent window into the past state of authorization.

The Code: The Smoking Gun

Let's look at the difference between "it works" and "it's secure." The vulnerability existed because the conversion function was blind to the context of the user's current permissions.

The Vulnerable Logic (Pseudo-code):

// Old way: Blindly convert DB rows to JSON
func ToStopWatches(stopwatches []Stopwatch) []APIStopwatch {
    var apiWatches []APIStopwatch
    for _, sw := range stopwatches {
        // Just grab the issue and repo details directly
        repo := GetRepo(sw.RepoID)
        issue := GetIssue(sw.IssueID)
        
        // Dump it into the response
        apiWatches = append(apiWatches, &APIStopwatch{
            Repo:  repo.Name,
            Issue: issue.Title,
        })
    }
    return apiWatches
}

The Fix (Commit 95ea2df):

The patch introduces two critical changes. First, it passes the requesting user (doer) into the function. Second, it explicitly checks CanReadIssuesOrPulls. If you can't read the issue anymore, the stopwatch is effectively invisible to the API response.

// The Fix: Trust but Verify
func ToStopWatches(ctx, stopwatches, doer) {
    for _, sw := range stopwatches {
        repo := GetRepo(sw.RepoID)
        
        // CRITICAL: Check permissions at runtime
        perm, _ := access_model.GetUserRepoPermission(ctx, repo, doer)
        
        // If the door is locked, don't tell them what's inside
        if !perm.CanReadIssuesOrPulls(sw.Issue.IsPull) {
            continue 
        }
 
        // Safe to add to response
        apiWatches = append(apiWatches, ...)
    }
}

This is the difference between asking "Did you have a key yesterday?" and "Do you have a key right now?"

The Exploit: Ex-Employee Revenge

Exploiting this requires no fancy tools—just standard API interactions. This is a "logic bug," which is my favorite kind because WAFs are useless against it.

Step 1: Infiltration Get added to a private repository. This could be legitimate employment, a contractor gig, or a compromised account that is temporarily added to a team.

Step 2: Tagging the Assets Browse the repository for sensitive issues. Maybe there's an issue named "Hardcoded AWS Keys in CI/CD" or "Layoff Strategy Q3". Start a stopwatch on every juicy issue you find. You don't need to log time; you just need to create the record in the DB.

Step 3: The Purge The admin removes your account from the organization. You verify via the UI: "404 Not Found" on the repository. The front door is locked.

Step 4: The Look-Back Query the API directly: GET /api/v1/user/stopwatches

The server responds with a JSON array containing the repo_name and issue_title for every stopwatch you started. You confirm the layoffs are happening. You see they haven't fixed the AWS keys yet. You have insider trading info, and you aren't even an insider anymore.

The Impact: Why Titles Matter

You might be thinking, "So what? I see a title. I don't see the code." Do not underestimate the value of metadata. In many organizations, issue titles contain high-level strategic information.

If I'm a competitor, knowing that you have an issue titled "Emergency Patch for Zero-Day in Payment Gateway" tells me everything I need to know about your current stability. If I'm an attacker, seeing "Refactor SQL Injection in Login" tells me exactly where to point sqlmap.

Furthermore, this persistence violates the fundamental security principle of Complete Mediation. Every access to every object must be checked for authority. Gitea failed this check, allowing a dangling reference to become an unauthorized peephole.

The Fix: Double Tap

The remediation provided by the Gitea team in version 1.25.4 is robust because it attacks the problem from two angles—the view layer and the data layer.

First, as detailed in the code section, the API presentation layer now filters out results you can't see. This stops the immediate bleeding. But leaving unauthorized records in the database is just messy hygiene.

So, the second part of the fix involves Active Cleanup. They modified services/repository/collaboration.go and services/org/team.go. Now, when a user is removed from a repository or a team, the backend triggers a cleanup routine RemoveStopwatchesByRepoID. It nukes the stopwatch records entirely.

If you are running Gitea <= 1.25.3, you are vulnerable. Upgrade immediately. If you can't upgrade, you need to manually audit your stopwatch database table for users who shouldn't be there, but frankly, upgrading is easier than writing SQL queries to cross-reference permission tables.

Official Patches

GiteaGitea 1.25.4 Release Notes
GitHubPull Request #36340

Fix Analysis (1)

Technical Appendix

CVSS Score
6.5/ 10
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
EPSS Probability
0.02%
Top 95% most exploited

Affected Systems

Gitea self-hosted instancesGitea Docker containers

Affected Versions Detail

Product
Affected Versions
Fixed Version
Gitea
Gitea
<= 1.25.31.25.4
AttributeDetail
CVE IDCVE-2026-20883
CWECWE-284 (Improper Access Control)
CVSS Score6.5 (Medium)
Attack VectorNetwork (API)
Fix Version1.25.4
ImpactInformation Disclosure

MITRE ATT&CK Mapping

T1213Data from Information Repositories
Collection
T1596Search Open Technical Databases
Reconnaissance
CWE-284
Improper Access Control

The software does not properly restrict access to a resource from an unauthorized actor.

Known Exploits & Detection

TheoryExploitation involves standard API calls to /user/stopwatches after permission revocation.

Vulnerability Timeline

Fix committed to main branch
2026-01-13
Gitea 1.25.4 Released
2026-01-22
CVE-2026-20883 Published
2026-01-22

References & Sources

  • [1]GHSA Advisory
  • [2]NVD CVE-2026-20883

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•about 2 hours ago•GHSA-C33X-VCV4-VG3G
2.1

GHSA-c33x-vcv4-vg3g: Missing Slot Identifier Validation in Cedar Policy Language JSON Template Parser Causes Panic and Authorization Divergence

An improper input validation vulnerability (CWE-20) exists in the Entity Syntax Tree (EST) JSON deserialization component of the Cedar Policy Language (`cedar-policy` and `@cedar-policy/cedar-wasm`). When parsing JSON-formatted policy templates that define `is ... in` scope constraints on `principal` or `resource`, the parser used wildcard pattern matching that ignored the provided slot identifier (`?principal` or `?resource`). This omission allows malformed JSON templates to bypass initial deserialization validation, resulting in either a runtime panic during template linking or inverted slot bindings that alter authorization outcomes.

Amit Schendel
Amit Schendel
4 views•10 min read
•about 3 hours ago•GHSA-W9X9-J56R-H98R
8.2

GHSA-w9x9-j56r-h98r: Unauthenticated Confused Deputy Proxy in @eeacms/volto-chatbot

The @eeacms/volto-chatbot package for Plone Volto contains an unauthenticated confused deputy vulnerability (CWE-862) in versions 2.0.3 and earlier. The package registers Express middleware on wildcard proxy routes (`**/_v1_da/**` and `**/_v1_ha/**`) without verifying client authentication or restricting target endpoints. When processing incoming HTTP requests, the middleware automatically attaches server-side administrative credentials before forwarding the requests to upstream Danswer (Onyx) or Halloumi (LLMGW) API servers. This allows unauthenticated network attackers to execute arbitrary administrative API calls against the backend infrastructure.

Amit Schendel
Amit Schendel
3 views•8 min read
•about 5 hours ago•CVE-2026-9705
6.5

CVE-2026-9705: Registration Access Token Authorization Bypass in Keycloak Dynamic Client Registration

CVE-2026-9705 is an insufficient session expiration and authorization bypass vulnerability in Keycloak and Red Hat build of Keycloak. The vulnerability occurs in the OAuth 2.0 Dynamic Client Registration Management Protocol implementation, where the authentication handler fails to verify whether a target client is enabled before accepting requests authenticated with a previously issued Registration Access Token (RAT). Consequently, an actor possessing a valid RAT for an administratively disabled client can query, update, and re-enable the client configuration, restoring revoked access to realm resources.

Amit Schendel
Amit Schendel
6 views•10 min read
•about 6 hours ago•CVE-2026-9800
8.1

CVE-2026-9800: Authorization Bypass in Keycloak Policy Enforcer via Unnormalized URI Substring Matching

CVE-2026-9800 is an authorization bypass vulnerability affecting the Keycloak Policy Enforcer (org.keycloak:keycloak-policy-enforcer), which is integrated into Red Hat build of Keycloak, Red Hat build of Quarkus, Red Hat Single Sign-On 7, and Red Hat JBoss Enterprise Application Platform Expansion Pack. The flaw stems from improper comparison logic in the PolicyEnforcer.isDefaultAccessDeniedUri method, which uses loose substring matching on the full raw request URI rather than exact, normalized path comparisons against the configured access-denied landing page. Consequently, an authenticated attacker possessing any valid low-privileged token can bypass role-based, scope-based, and User-Managed Access (UMA) policy evaluations by embedding the access-denied path segment into the URL path or query parameters.

Amit Schendel
Amit Schendel
5 views•8 min read
•about 7 hours ago•CVE-2026-9799
4.6

CVE-2026-9799: Authorization Bypass via UMA Permission Ticket Wildcard in Keycloak

CVE-2026-9799 is an authorization bypass vulnerability in the User-Managed Access (UMA) subsystem of Keycloak (org.keycloak.authorization). When a resource server is configured with the non-default PERMISSIVE policy enforcement mode, an authenticated user holding a valid UMA permission ticket for a single typed resource can obtain an authorization Requesting Party Token (RPT) granting access to all resources of the same type across the resource server. By submitting an authorization request with the type selector prefix 'resource-type-any:<type>', the attacker bypasses instance-level owner approval checks for unshared peer resources that lack explicit static administrative policies.

Alon Barad
Alon Barad
4 views•9 min read
•about 8 hours ago•CVE-2026-9083
4.9

CVE-2026-9083: Arbitrary Filesystem Path Probing in Keycloak Java Keystore Key Provider

CVE-2026-9083 is a path traversal and information disclosure vulnerability (CWE-22) affecting Keycloak and the Red Hat build of Keycloak. The vulnerability resides in the Java Keystore key provider component (JavaKeystoreKeyProviderFactory), which fails to restrict the user-supplied keystore file path to a designated directory. An authenticated realm administrator holding the 'manage-realm' role can supply arbitrary absolute or relative filesystem paths when configuring a key provider. Because the application returns distinct error messages depending on whether the target path does not exist, lacks read permissions, or fails keystore parsing, an attacker can use this component as an oracle to probe the underlying host filesystem.

Alon Barad
Alon Barad
6 views•11 min read