Jan 24, 2026·5 min read·128 visits
If you fire an employee and revoke their Git access, they might still be watching your repo. In Gitea versions prior to 1.25.4, the release mailer didn't double-check permissions before hitting 'Send'. This resulted in private release notes, titles, and tags being broadcast to users who should have been locked out. The fix ensures permissions are validated at the moment of dispatch and wipes the watcher list when a repo goes private.
A logic flaw in Gitea's notification system allowed unauthorized users—specifically 'watchers' who lost access or remained subscribed after a repository went private—to continue receiving detailed release emails containing private changelogs and tags.
Imagine this scenario: You have a developer, let's call him 'Bob'. Bob is a bit of a loose cannon, so you revoke his access to your sensitive private repositories. You lock the doors, change the digital locks, and feel secure. But next Tuesday, when you push a new release titled 'v2.0 - Fixed Critical Auth Bypass', Bob gets an email about it.
He doesn't have access to the code anymore, but he knows exactly what you shipped, when you shipped it, and thanks to the detailed changelog you meticulously wrote, he knows exactly where your security bodies are buried.
This is the essence of CVE-2026-0798. It’s not a buffer overflow or a fancy ROP chain. It’s a classic logic error where the application confused 'interest' (watching a repo) with 'authorization' (actually having the right to see it). Gitea, the popular self-hosted Git service, forgot that just because someone wants to know about a release, doesn't mean they should.
The vulnerability stems from two distinct but cooperating failures in Gitea's state management. The first is a failure of sanitization during state transition. When a repository administrator toggles a repo from 'Public' to 'Private', the system flips a boolean flag in the database. What it didn't do was evict the current audience. The list of 'watchers'—users subscribed to notifications—remained intact. This is like turning a public park into a private club but forgetting to ask the people sitting on the benches to leave.
The second, and more critical failure, was a Time-of-Check to Time-of-Use (TOCTOU) style logic gap in the mailer service. When a new release is cut, Gitea grabs the list of watchers. In the vulnerable versions, it treated this list as a trusted source of truth for recipients. It assumed that if you were on the list, you belonged there.
It never paused to ask: 'Does this user currently have read access to this now-private repository?' It just looped through the IDs and fired off emails. This meant that revoking a user's repository access didn't actually stop the information flow—it just stopped them from clicking the link, but the email content (the leak) was already delivered.
Let's look at the fix to understand the break. The vulnerability lived in services/mailer/mail_release.go. The original code simply iterated over recipients and sent the mail. The patch introduces a necessary permission check.
Here is the diff that stopped the leak. Notice the introduction of access_model.CheckRepoUnitUser. This function is the bouncer that should have been there all along.
// services/mailer/mail_release.go
// BEFORE: Iterate and spray
// for _, user := range recipients {
// SendMail(user, ...)
// }
// AFTER: Validate before sending
recipients = slices.DeleteFunc(recipients, func(u *user_model.User) bool {
// If the user is the publisher, they get a pass.
// Otherwise, check if they actually have READ access to Releases.
return u.ID == rel.PublisherID ||
!access_model.CheckRepoUnitUser(ctx, rel.Repo, u, unit.TypeReleases)
})Additionally, they patched the 'Public to Private' transition logic in services/repository/repository.go. Now, when you hide a repo, Gitea nukes the watcher list entirely:
// services/repository/repository.go
func MakeRepoPrivate(ctx context.Context, repo *repo_model.Repository) (err error) {
// ...
// The new "Reset Button"
if err = repo_model.ClearRepoWatches(ctx, repo.ID); err != nil {
return err
}
// ...
}This is a nuclear approach to the problem—unsubscribing everyone—but it safely defaults to 'secure' rather than 'convenient'.
Exploiting this requires zero active hacking tools. No Burp Suite, no Metasploit. It relies entirely on the configuration lifecycle of the target organization. This is a persistence technique.
Scenario 1: The Insider Threat
Scenario 2: The Pivot
The CVSS score is a measly 3.5 (Low). This is because it requires specific preconditions (Low Privileges) and only leaks 'Confidentiality' of the release metadata. But don't let the score fool you into ignoring it.
In the real world, developers are chatty. Release notes often contain:
Information disclosure is the precursor to compromise. Knowing what is changing in a private codebase gives an attacker the roadmap they need to launch a more targeted attack once they find another way in, or allows them to exploit the knowledge of patched vulnerabilities against unpatched instances.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Gitea Gitea | <= 1.25.3 | 1.25.4 |
| Attribute | Detail |
|---|---|
| CWE | CWE-284 (Improper Access Control) |
| CVSS v3.1 | 3.5 (Low) |
| Attack Vector | Network |
| Attack Complexity | Low |
| Privileges Required | Low (Requires previous access) |
| Impact | Information Disclosure |
The software does not restrict or incorrectly restricts access to a resource from an unauthorized actor.
CVE-2026-115907 is an uncontrolled resource consumption vulnerability in the Rust zip crate (versions 5.1.0 through 8.6.0, and 9.0.0 pre-releases) when compiled with the optional legacy-zip feature flag. The vulnerability resides in the legacy decompression decoders—Shrink, Reduce, and Implode—which directly pass an archive-declared 64-bit uncompressed size from a ZIP64 extended information extra field to Vec::reserve() before validating the compressed data stream. By supplying a malformed ZIP archive with a declared uncompressed size exceeding isize::MAX (such as u64::MAX), an unauthenticated attacker can trigger an unhandled capacity overflow panic in the Rust standard library allocator, terminating the calling thread or aborting the entire process.
Twisted versions up to and including 26.4.0 are vulnerable to an unauthenticated remote denial-of-service attack via the HTTP/2 Rapid Reset technique (a variant of CVE-2023-44487). The HTTP/2 connection handler in twisted.web processes pipelined HEADERS and RST_STREAM frames sequentially within the same TCP segment. Because the request dispatch and synchronous rendering occur upon receiving the StreamEnded event before the subsequent StreamReset event is evaluated, an attacker can force the server to expend CPU cycles rendering responses while immediately evicting stream state to bypass concurrent stream limits.
An inefficient algorithmic complexity vulnerability exists in the npm package decode-uri-component prior to version 0.5.1. When processing malformed URI strings containing invalid percent-escape sequences, the library falls back to a custom decoder that collects distinct percent-encoded runs into a dictionary and executes a full-string regular expression replacement for each entry. Supplying a payload with thousands of distinct percent-encoded sequences alongside a malformed escape triggers quadratic O(K * L) execution time, starving the single-threaded Node.js event loop and resulting in an unauthenticated Denial of Service.
Open Neural Network Exchange (ONNX) versions 1.4.0 prior to 1.22.0 contain a null pointer dereference (CWE-476) and a signed-to-unsigned integer conversion error (CWE-195) in the shape inference functions for the Scan operator. When processing an untrusted ONNX model, the functions ScanInferenceFunction, ScanInferenceFunction_opset8, and ScanInferenceFunction_opset9 fail to validate the presence and value of the required num_scan_inputs attribute. Omitting the attribute results in a direct null pointer dereference that crashes the host process via SIGSEGV, while supplying a negative value triggers an integer wrap-around during type narrowing that leads to an unhandled allocation failure. Notably, models containing a negative num_scan_inputs value bypass standard validation via onnx.checker.check_model().
CVE-2026-91403 is a critical cryptographic trust validation flaw (CWE-327) in the Java Secure Socket Extension (JSSE) component of Oracle Java SE, Oracle GraalVM, and OpenJDK builds. Triggered by the compromise of private signing keys for eight intermediate Certificate Authorities operated by eMudhra (emSign), unpatched Java runtime environments implicitly trust fraudulent TLS and code-signing certificates issued by these compromised intermediates. Because Java disables online certificate revocation checking (OCSP/CRL) by default, an unauthenticated network attacker in an Adversary-in-the-Middle (AiTM) position can intercept, decrypt, and modify encrypted TLS communications or bypass code signature verification without triggering security exceptions.
Open Neural Network Exchange (ONNX) prior to version 1.22.0 contains a memory exhaustion vulnerability (CWE-789) in its Python tensor unpacking helpers and C++ model checker. Specifically, the _unpack_2bit() and _unpack_4bit() functions in onnx.numpy_helper unconditionally resize unpacked NumPy arrays to match attacker-controlled TensorProto.dims metadata without verifying that the underlying raw_data or int32_data buffers contain a corresponding number of packed bits. Furthermore, onnx.checker.check_tensor() previously bypassed length verification for sub-byte data types. Processing an untrusted ONNX model with packed sub-byte tensors (UINT2, INT2, UINT4, INT4, or FLOAT4E2M1) allows an unauthenticated attacker to trigger disproportionate memory allocations from minimal payloads, resulting in an out-of-memory (OOM) crash and denial of service.