CVE-2026-58441: Gitea SSRF Vulnerability
MODERATECVE-2026-58441 is a MODERATE (CVSS 6.3) ssrf vulnerability (CWE-918: Server-Side Request Forgery (SSRF)) affecting Gitea libraries in the Go ecosystem. The issue involves ssrf in restore-repo via unsanitized pull_request.yml head.cloneurl. Remediation is available by upgrading affected packages to version 1.27.0.
CVE-2026-58441 at a Glance
Security Impact
CVSS: 6.3
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N).
How to Fix CVE-2026-58441
To remediate this vulnerability, upgrade all affected Gitea dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
Upgrade code.gitea.io/gitea to 1.27.0
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
inspect dependencies for code.gitea.io/gitea
How to Check if You Are Affected
To determine whether your application is affected by CVE-2026-58441, inspect your project dependencies for Gitea:
inspect dependencies for code.gitea.io/gitea
Analyze the terminal output to locate any version of Gitea in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.27.0), your application is affected.
Note: Verify declared build manifests and deployed runtime container environments.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-918: Server-Side Request Forgery (SSRF), where the web server receives a url or similar request from an upstream component and retrieves the contents of this url, but it does not sufficiently ensure that the request is being sent to the expected destination. In Gitea, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-58441 Affected Gitea Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
code.gitea.io/gitea
|
Go |
< 1.27.0
|
1.27.0
|
References
Frequently Asked Questions
What is CVE-2026-58441?
Which Gitea versions are affected?
What version fixes CVE-2026-58441?
Is code.gitea.io/gitea affected?
What is the CVSS score of CVE-2026-58441?
What CWE is associated with CVE-2026-58441?
How can I check Go dependencies for CVE-2026-58441?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-xmj7-xj85-hfc3",
"modified" : "2026-07-21T20:37:29Z",
"published" : "2026-07-21T20:37:29Z",
"aliases" : [ "CVE-2026-58441" ],
"summary" : "Gitea: SSRF in restore-repo via unsanitized pull_request.yml Head.CloneURL",
"details" : "### Summary\nGitea's `restore-repo` CLI command restores a repository from a dump\ndirectory/archive. When parsing `pull_request.yml` from that dump, the\n`Head.CloneURL` field is used to add a git remote and fetch from it with\nno validation, because the safety check that's supposed to guard it\n(`CheckAndEnsureSafePR`) is called with an empty `commonCloneBaseURL`,\nwhich silently disables it. This lets a malicious dump make the Gitea\nserver execute `git fetch` against an attacker-chosen URL (SSRF), or\ndisclose a local git repository via `file://`. This is a different root\ncause from the recently fixed path-traversal issue in the same command\n(#38215), which patched `DownloadURL`/`PatchURL` but not `Head.CloneURL`.\n\n### Details\n`services/migrations/restore.go`'s `GetPullRequests()` unmarshals\n`pull_request.yml` directly into `base.PullRequest` structs with no\nvalidation of `Head.CloneURL`:\n\n```go\nerr = yaml.Unmarshal(bs, &pulls)\n...\nfor _, pr := range pulls {\n if pr.PatchURL != \"\" {\n pr.PatchURL = \"file://\" + util.FilePathJoinAbs(r.baseDir, pr.PatchURL)\n }\n CheckAndEnsureSafePR(pr, \"\", r) // <-- empty baseURL\n}\n```\n\n`CheckAndEnsureSafePR` (`services/migrations/common.go`) is supposed to\nreject `Head.CloneURL`/`PatchURL` values that don't share a common base\nURL:\n\n```go\nfunc hasBaseURL(toCheck, baseURL string) bool {\n if len(baseURL) > 0 && baseURL[len(baseURL)-1] != '/' {\n baseURL += \"/\"\n }\n return strings.HasPrefix(toCheck, baseURL)\n}\n\nfunc CheckAndEnsureSafePR(pr *base.PullRequest, commonCloneBaseURL string, g base.Downloader) bool {\n valid := true\n if pr.PatchURL != \"\" && !hasBaseURL(pr.PatchURL, commonCloneBaseURL) {\n pr.PatchURL = \"\"\n valid = false\n }\n if pr.Head.CloneURL != \"\" && !hasBaseURL(pr.Head.CloneURL, commonCloneBaseURL) {\n pr.Head.CloneURL = \"\"\n valid = false\n }\n return valid\n}\n```\n\n`strings.HasPrefix(anything, \"\")` is always `true` in Go. Because\n`restore.go` is the only caller that passes `\"\"` as\n`commonCloneBaseURL`, this check is a complete no-op on the restore-repo\npath — `Head.CloneURL` survives unchanged regardless of its value. Every\nother downloader (`github.go`, `gitlab.go`, `gitea_downloader.go`,\n`codebase.go`, `codecommit.go`, `onedev.go`) passes a real base URL, so\nthey are not affected.\n\n`services/migrations/gitea_uploader.go` then uses the unvalidated value\ndirectly:\n\n```go\nerr := g.gitRepo.AddRemote(remote, pr.Head.CloneURL, true)\n// ... later: fetch from that remote\n```\n\nresulting in the server executing `git fetch` against an\nattacker-controlled URL sourced from the dump file.\n\n**RCE via git's `ext::` transport helper was tested and ruled out** — a\nnormal `git` install rejects it by default (`fatal: transport 'ext' not\nallowed`), independent of Gitea's own configuration. This report is\nscoped to SSRF and local git-repository disclosure.\n\nConfirmed present, byte-for-byte identical, in `v1.26.4` (latest stable\ntag), `release/v1.27`, and `main`, by direct checkout and diff.\n\n### PoC\n1. Create a dump directory following the normal `restore-repo` layout\n (`repo.yml`, etc.), and add a `pull_request.yml` containing at least\n one entry with:\n```yaml\n - number: 1\n head:\n cloneURL: \"http://<attacker-controlled-or-internal-host>:<port>/ssrf-proof\"\n ref: \"main\"\n```\n2. Run `gitea restore-repo` against that dump directory for any repo\n owner.\n3. Observe on the target host/listener: an actual `git` HTTP\n discovery request arrives, e.g.\n `GET /ssrf-proof/info/refs?service=git-upload-pack`, driven entirely\n by the value from the dump file.\n\nVerified the core mechanism (steps 2–3, i.e. the unvalidated\n`Head.CloneURL` surviving `CheckAndEnsureSafePR(\"\")` and then being used\nin a real `git remote add` + `git fetch`) with a minimal, standalone Go\nprogram built from the **verbatim, unmodified** `hasBaseURL` /\n`CheckAndEnsureSafePR` function bodies (attached: `gitea_ssrf_poc.go`),\nrun end-to-end against a local HTTP listener. The listener's access log\nconfirms the request actually arrives. \n\n### Impact\nAn attacker who can get an administrator to run `gitea restore-repo`\nagainst a malicious dump (the same threat model already accepted for the\njust-fixed path-traversal issue in this command, #38215) can make the\nGitea server issue a `git fetch` against an arbitrary attacker-chosen\nURL. This allows:\n- SSRF against internal-only services or cloud metadata endpoints\n reachable from the Gitea host.\n- Disclosure of local git repositories reachable via `file://` paths\n readable by the Gitea process.\n\nNo public disclosure planned. Happy to provide further detail on\nrequest.",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "Go",
"name" : "code.gitea.io/gitea"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "1.27.0"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/go-gitea/gitea/security/advisories/GHSA-xmj7-xj85-hfc3"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/go-gitea/gitea"
}, {
"type" : "WEB",
"url" : "https://github.com/go-gitea/gitea/releases/tag/v1.27.0"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-918" ],
"severity" : "MODERATE",
"github_reviewed" : true,
"github_reviewed_at" : "2026-07-21T20:37:29Z",
"nvd_published_at" : null
}
}