GHSA-pgqf-926r-548m CVE-2026-58445 GO-2026-6067
Gitea: Cross-repository label-ID enumeration oracle via unscoped DeleteIssueLabel API
- Severity
- 2.7 / 10
- Low · CVSS 3.1
- Exploit chance (EPSS)
- 0.37%
- Chance of exploitation in the next 30 days. Higher than 30.50% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Fix
- Available
- 2 packages have a fixed version
Description
Summary
The API endpoint DELETE /repos/{owner}/{repo}/issues/{index}/labels/{id} loads the label by ID with a global,
unscoped lookup and never verifies the label belongs to the URL's repository (or its owning organization). Because
the response status differs by whether the label ID exists anywhere on the instance (204) versus not (422), an
authenticated user can use the endpoint as a cross-repository label-ID existence / enumeration oracle, including
for labels in repositories and organizations they cannot access.
Severity
- The leaked information is minimal (existence/count of label IDs instance-wide); no label name, color, or owning repository is disclosed, and no cross-repository write occurs.
Affected / patched versions
- Affected: through 1.26.3 (latest at time of report).
- Patched: none yet.
Details
DeleteIssueLabel resolves the label with a global loader and never checks its scope:
// routers/api/v1/repo/issue_label.go (DeleteIssueLabel)
label, err := issues_model.GetLabelByID(ctx, ctx.PathParamInt64("id")) // global, unscoped
GetLabelByID (models/issues/label.go) is e.ID(labelID).Get(l) with no repo_id / org_id filter. The
handler never verifies label.RepoID == ctx.Repo.Repository.ID (nor the org-label equivalent), and the downstream
issue_service.RemoveLabel (services/issue/label.go) only re-checks the doer's write permission on the issue's
own repository — never that the label belongs to it.
Every sibling label handler is correctly scoped — GetLabel / EditLabel / DeleteLabel (repo and org) use
GetLabelInRepoByID / GetLabelInOrgByID and return 404 for a foreign ID. DeleteIssueLabel is the only outlier.
Why it is only an oracle: deleteIssueLabel (models/issues/issue_label.go) deletes the issue_label row keyed
by (issue.ID, label.ID). For a foreign label, no such row exists → the function returns early before any
mutation or comment creation. So there is no cross-repo write and no leak of the label's name. But the HTTP status
differs:
- label ID exists anywhere on the instance (incl. private repos/orgs) → 204 No Content
- label ID does not exist → 422 (
ErrLabelNotExist)
Label IDs are sequential auto-increment, so this enumerates the instance-wide label population and probes existence of specific IDs across tenant boundaries.
Proof of Concept
Verified end-to-end on a build of the v1.26.3 tag.
alice(private repoalice/secret) creates a label → internal id 1.- Attacker
bob(separate user; public repobob/pubwith issue #1; no access toalice/secret) holds a token withwrite:issueon his own repo.
bob DELETE /api/v1/repos/bob/pub/issues/1/labels/1 -> HTTP 204 (alice's PRIVATE label id exists)
bob DELETE /api/v1/repos/bob/pub/issues/1/labels/99999999 -> HTTP 422 (no such label)
Differing only by the label ID: 204 vs 422 distinguishes "label ID exists" from "does not exist." bob has zero rights to alice/secret but can still learn label id 1 exists. (Alice's label is untouched — no write.)
Reproduction steps:
- Create two users
alice,bob. Asalice, create a private repo and a label on it (note the labelidfrom the API response). - As
bob, create any repo with an issue, and a token withwrite:issue. curl -u bob:$T -X DELETE https://<gitea>/api/v1/repos/bob/pub/issues/1/labels/<alice_label_id>→ 204.curl -u bob:$T -X DELETE https://<gitea>/api/v1/repos/bob/pub/issues/1/labels/99999999→ 422.- The differing status across an ID
bobcannot otherwise see is the oracle.
Impact
Cross-tenant authorization-key bypass producing a label-ID existence/enumeration oracle: an authenticated user can determine whether arbitrary label IDs (including in private repositories and organizations they cannot access) exist, and enumerate the instance-wide label population.
Remediation
Scope the loader like every sibling handler, returning 404 for a label that is not in the URL repository (or its owning organization), so the status no longer distinguishes existence:
// routers/api/v1/repo/issue_label.go — in DeleteIssueLabel, after loading the label
if label.RepoID != ctx.Repo.Repository.ID && label.OrgID != ctx.Repo.Repository.OwnerID {
ctx.APIErrorNotFound()
return
}
(Equivalently, resolve via GetLabelInRepoByID and, for org repositories, also accept the repo owner's org labels —
mirroring the scoping in GetLabel/EditLabel/DeleteLabel.)
Affected versions
Go
| Package | Vulnerable | Fixed in |
|---|---|---|
| code.gitea.io/gitea | < 1.27.0 |
1.27.0 |
| gitea.dev | < 1.27.0 |
1.27.0 |
How to fix
Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.
code.gitea.io/gitea Go → 1.27.0
- go
go get code.gitea.io/gitea@v1.27.0 && go mod tidy
gitea.dev Go → 1.27.0
- go
go get gitea.dev@v1.27.0 && go mod tidy
Severity breakdown
CVSS 3.1 2.7
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N
- Attack vector
- Network
- Attack complexity
- Low
- Privileges required
- High
- User interaction
- None
- Scope
- Unchanged
- Confidentiality
- Low
- Integrity
- None
- Availability
- None
Weakness
CWE-203: Observable Discrepancy
The product behaves differently or sends different responses under different circumstances in a way that is observable to an unauthorized actor.
How to prevent it
- Architecture and Design: Compartmentalize the system to have "safe" areas where trust boundaries can be unambiguously drawn. Do not allow sensitive data to go outside of the trust boundary and always be careful when interfacing with a compartment outside of the safe area. Ensure that appropriate compartmentalization is built into the system design, and the compartmentalization allows for and reinforces privilege separation functionality. Architects and designers should rely on the principle of least privilege to decide the appropriate time to use privileges and the time to drop privileges.
- Implementation: Ensure that error messages only contain minimal details that are useful to the intended audience and no one else. The messages need to strike the balance between being too cryptic (which can confuse users) or being too detailed (which may reveal more than intended). The messages should not reveal the methods that were used to determine the error. Attackers can use detailed information to refine or optimize their original attack, thereby increasing their chances of success. If errors must be captured in some detail, record them in log messages, but consider what could occur if the log messages can be viewed by attackers. Highly sensitive information such as passwords should never be saved to log files. Avoid inconsistent messaging that might accidentally tip off an attacker about internal state, such as whether a user account exists or not.
CWE-639: Authorization Bypass Through User-Controlled Key
The system's authorization functionality does not prevent one user from gaining access to another user's data or record by modifying the key value identifying the data.
How to prevent it
- Architecture and Design: For each and every data access, ensure that the user has sufficient privilege to access the record that is being requested.
- Architecture and Design, Implementation: Make sure that the key that is used in the lookup of a specific user's record is not controllable externally by the user or that any tampering can be detected.
- Architecture and Design: Use encryption in order to make it more difficult to guess other legitimate values of the key or associate a digital signature with the key so that the server can verify that there has been no tampering.
References
Packages
Other links
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| GHSA-rcr6-4jqh-j84m Gitea: Remote Code Execution via diffpatch Git Hook Installation | Critical Known exploited | Also affects code.gitea.io/gitea |
| GHSA-fq2p-5p22-8g6j Gitea: Public-Only Personal access tokens scope bypass in Organization and Permission Endpoints | Medium | Also affects code.gitea.io/gitea |
| GHSA-2wm4-vwp6-v7xc Gitea: SSRF via Migration Asset Downloads Bypasses hostmatcher — Reads Internal Files and Cloud Metadata | High | Also affects code.gitea.io/gitea |
| GHSA-3r5c-2xxx-h872 Gitea: Webhook Authorization Header Returned in Plaintext via API | Low | Also affects code.gitea.io/gitea |
| GHSA-frpw-3h2q-4jj6 Gitea: Cross-Repo Information Disclosure via Org-Level Actions Run/Job APIs | Medium | Also affects code.gitea.io/gitea |
| GHSA-q423-49rw-g9mh Gitea: GHSA-8fwc-qjw5-rvgp ClearRepoWatches fix not applied to API EditRepo path — sister code path retains stale watches on public->private | Medium | Also affects code.gitea.io/gitea |
| GHSA-h56g-4qw7-2mxg Gitea: Public-only API token restriction is not enforced on team API routes | Medium | Also affects gitea.dev |
| GHSA-prr9-9mp4-5gp2 Gitea: Private org member list leaked via /members API endpoint — incomplete fix for PR #38145 | High | Also affects gitea.dev |
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor