On October 18 at 18:25 UTC Gitea shipped v28.2.0: two enhancements and thirty-three entries under "Bug Fixes" — one of which is a golang.org/x/net bump tagged [security]. No CVE appears anywhere in the notes. On the same day, the project's GitHub security advisories page gained seven entries — published by the same maintainer (bircni) who wrote the release post. The October 6 batch of three (fixed in 28.1.0) had gone up twelve days earlier the same way.

This is the Keycloak pattern from earlier this month again: the release notes are written for the front page and the changelog feed; the CVE accounting lives in an XML feed nobody subscribes to. If you upgrade on release notes alone, you do not know what you are upgrading from — or what you are exposed to if you stay.

The seven

All version ranges and severities below are the advisory metadata as published October 18. Severity is GitHub's label; we note the CVSS vector where it is informative.

| GHSA | CVE | What an attacker gets | Affected → Patched | |---|---|---|---| | GHSA-wx44 | CVE-2026-107686 | Any signed-in user keeps adding/removing emoji reactions on a locked issue or PR through the web routes — the API checks the lock (403), the two web handlers never did | ≤ 28.1.0 → 28.2.0 | | GHSA-4jmf | CVE-2026-107618 | CSRF on POST /{owner}/{repo}/topics: the topics route is registered without sign-in middleware, so the cross-origin check never runs — a malicious website can overwrite the topics of any repository whose admin visits it while logged in. Topics are discovery metadata, so this is repo defacement with search consequences | ≤ 28.1.0 → 28.2.0 | | GHSA-8j56 | CVE-2026-110311 | Creating a draft release returns instance-wide attachment metadata — filenames, UUIDs, sizes, download counts — from private repositories and abandoned uploads the caller cannot read, to any user who can create a draft release. On an open-registration instance that is anyone with an account | ≥ 1.22.0, ≤ 28.1.0 → 28.2.0 | | GHSA-fxq4 | CVE-2026-97204 | DeleteLabel compares one untyped ID against whichever namespace the label turns out to use. Repo IDs and org IDs come from independent sequences and collide freely, so a repo collaborator deletes labels belonging to an organisation they have no standing in. Reporter scored attack complexity High; it is a missing type check, not a practical targeted attack | ≤ 28.1.0 → 28.2.0 | | GHSA-96gv | CVE-2026-109844 | GET /api/v1/user/subscriptions leaks private repository metadata (names, visibility, clone URLs, topic, branch/issue counts) after a team permission downgrade — the permission object is computed and then formatted instead of enforced. Fixed in 1.27.0, disclosed October 18 — a late disclosure, and the reason you still patch old branch releases you already run | ≤ 1.26.4 → 1.27.0 | | GHSA-cwq7 | CVE-2026-110494 | AddReleaseAttachments misses the issue-bound check its sibling validateAttachmentForIssue got in the CVE-2026-57886 fix. A user with release write but issue read can re-link a known issue attachment UUID to a release, expose its metadata, rename it, and delete it — record and file — permanently. Requires releases-write without issue-read: a non-default but supported configuration | ≤ 28.1.0 → 28.2.0 | | GHSA-6f9g | CVE-2026-105964 | Five Actions Artifact V4 endpoints io.ReadAll the entire request body with no limit. An authenticated user who can trigger a workflow uses the runner token to send one oversized request and OOM-kills the process. No availability score games here: on the single-binary layout most community instances run, Gitea runs until the OOM kill and then it is down | ≥ 1.22.0, ≤ 28.1.0 → 28.2.0 |

Three worth sitting with.

The draft-release leak is the quiet one. The mechanism in the advisory is elegant in the way only real bugs are: createTag() loads attributes on a release row before it is inserted, so GetReleaseAttachments runs release_id IN (0) — and issue/comment attachments are stored with release_id = 0, so every such attachment on the entire instance matches. The API then serialises all of it back. Every comment and issue attachment on the instance that wasn't linked to a release — across private repositories included — plus every abandoned upload, was one API call away from anyone with an account, via a draft release in a repo they had just created. The fix stops the leak; it does not un-leak the filenames, which on a community instance may say a great deal.

The artifact DoS is the availability story. For the community deployments this site serves, Gitea is the forge and probably Actions. The advisory's impact statement is refreshingly concrete: the memory exhaustion is "unrecoverable" — the process dies, everything on that process dies. The exploit prerequisite is a runner token, i.e. the ability to trigger a workflow; if you run Actions with workflows on fork PRs, that bar is low. Disable Actions on instances you do not depend on it, and treat Actions-enabled community forges as availability-critical infrastructure, because CVE-2026-105964 just said out loud that they are.

The tags/delete one from October 6 deserves the same sentence: GHSA-vvqw let any Code:Write collaborator destroy a release and its attachment blobs through the bare-tag delete route, because a private handler could not tell a tag from a release. Releases are immutable artifacts in most community workflows — that one is why you go to 28.1.0 or better even if you decide the other six are noise.

What the release notes actually say

Five of the seven map onto three nondescript lines in the 28.2.0 changelog. The sixth was fixed back in the 1.27.0 generation. The seventh has no line at all:

  • "Correct label deletion and release attachment load (#39743)" — that line covers CVE-2026-97204 (label deletion) and, via the attachment-load half, the two attachment re-linking/leak CVEs.
  • "(actions) Bound request body size in artifacts v4 twirp handlers (#39818)" — CVE-2026-105964.
  • "Add missing checks to several handlers and models (#39839)" — among other things, CVE-2026-107686.

The CSRF fix (CVE-2026-107618) matches nothing by name. Not one of the seven is listed as a security fix, because no security section exists. The only [security]-tagged entry is the dependency bump, which fixes none of the seven.

The release also carries fixes with security bearing that no advisory covers and the notes do not explain: Validate "state" for OpenID (#39642), Validate OID before building filesystem object path in LFS (#39762), Resolve and proxy srcset links in markup (#39708), Prevent competing remember-me token rotations (#39686), and Reject incoming email tokens of users who cannot sign in (#39833). Experience with this project and others says some of these surface as advisories later. The release notes never tell you which. That is the gap, and it is not Gitea-specific — Nextcloud's advisory stream has the same shape, Keycloak's release tags miscounted its own CVEs this month. The fix on your side is habit, not trust: subscribe to the GitHub advisories feed of everything you run, and read release notes as marketing.

One more operational note: the page about.gitea.com/security/ — the one a search result sends you to — is a static table whose newest CVE is from 2022. The real feed is the GitHub advisories page. Gitea's own "security" page is four years stale; plan accordingly when you are documenting your own incident-response runbook.

The reporters have changed

The credits across the seven advisories read like this: two of them carry GitHub's "This report was submitted with AI assistance" banner; manus-use is a credited reporter on three of the seven; manus-pi is named as reporter inside one of today's advisories and on one of October 6's; one reporter wrote "I'm not a security researcher; I'm an open source maintainer" (danielroe, who found the draft-release leak while recording fixture responses for a cross-forge client — the good old way). N0K0 is credited on four of the ten advisories published in the last twelve days, three of them with full file/line reproduction code. bircni and wxiaoguang did most of the remediation on both days.

No editorialising needed, just the observation this site keeps coming back to: the bugs mass-produced review finds are CWE-862, 863, 285, 639 — missing or misapplied authorization checks at hand-written handler boundaries. The web/API split (API enforces, web route forgot) shows up twice in today's batch alone. If the economics of vuln discovery shift toward agents, the class of bug that shifts with them is the kind a single missing boolean causes — and the defense is boring to the point of ritual: route-level middleware that cannot be forgotten, a shared permission gate, tests that assert 403. Gitea's advisories are, read together, a free audit of its own handler inventory, written by its adversaries.

What to actually do

Running 28.1.0: upgrade to 28.2.0. It is a patch release, no configuration work, and the same-day advisory set is the release note that matters. Gitea Cloud instances upgrade themselves.

Running 28.0.0 or 28.1.0 with Actions enabled, public or semi-public: same upgrade, and check your workflow trigger settings. The OOM kill does not need an exploit chain, just a token and one request.

Pinned to the 1.27.x LTS branch: this is the hard one. The affected ranges of the October 18 six say ≤ 28.1.0, which includes every 1.27 build, and as of tonight no 1.27.4 exists — the fixed-in for CVE-2026-109844 shows the branch does get CVE patches, so one may appear; it does not exist at publication. Add the October 6 batch (≤ 28.0.0, fixed in 28.1.0) and a 1.27.x machine counts as exposed to nine of the ten advisories Gitea has published in the last two weeks — the only one it already carries the fix for is CVE-2026-109844, fixed in its own 1.27.0. The honest options: migrate to 28.2.0 — re-read what the 28.0 jump actually is: configuration changes, budgeted as a project, not an apt upgrade — or hold at 1.27.3 and cut the exposure deliberately: disable Actions where you do not need it (removes CVE-2026-105964's surface), restrict releases-write to people who also hold issue-read (blunts CVE-2026-110494), and audit your FORCE_PRIVATE story, because CVE-2026-89182 — push-options bypassing the site-wide force-private policy, fixed in 28.1.0 — is the one that breaks the thing private community forges exist to keep private.

And whatever forge you run: this site said it in September about the 1.27 branch, and the October 18 drop proves the cadence: watch the advisories feed, not the blog. The blog said "released"; the advisories said "why".