CVE-2026-58441
Published:July 21, 2026
Updated:August 02, 2026
Summary Gitea's "restore-repo" CLI command restores a repository from a dump directory/archive. When parsing "pull_request.yml" from that dump, the "Head.CloneURL" field is used to add a git remote and fetch from it with no validation, because the safety check that's supposed to guard it ("CheckAndEnsureSafePR") is called with an empty "commonCloneBaseURL", which silently disables it. This lets a malicious dump make the Gitea server execute "git fetch" against an attacker-chosen URL (SSRF), or disclose a local git repository via "file://". This is a different root cause from the recently fixed path-traversal issue in the same command (#38215), which patched "DownloadURL"/"PatchURL" but not "Head.CloneURL". Details "services/migrations/restore.go"'s "GetPullRequests()" unmarshals "pull_request.yml" directly into "base.PullRequest" structs with no validation of "Head.CloneURL": err = yaml.Unmarshal(bs, &pulls) ... for _, pr := range pulls { if pr.PatchURL != "" { pr.PatchURL = "file://" + util.FilePathJoinAbs(r.baseDir, pr.PatchURL) } CheckAndEnsureSafePR(pr, "", r) // <-- empty baseURL } "CheckAndEnsureSafePR" ("services/migrations/common.go") is supposed to reject "Head.CloneURL"/"PatchURL" values that don't share a common base URL: func hasBaseURL(toCheck, baseURL string) bool { if len(baseURL) > 0 && baseURL[len(baseURL)-1] != '/' { baseURL += "/" } return strings.HasPrefix(toCheck, baseURL) } func CheckAndEnsureSafePR(pr *base.PullRequest, commonCloneBaseURL string, g base.Downloader) bool { valid := true if pr.PatchURL != "" && !hasBaseURL(pr.PatchURL, commonCloneBaseURL) { pr.PatchURL = "" valid = false } if pr.Head.CloneURL != "" && !hasBaseURL(pr.Head.CloneURL, commonCloneBaseURL) { pr.Head.CloneURL = "" valid = false } return valid } "strings.HasPrefix(anything, "")" is always "true" in Go. Because "restore.go" is the only caller that passes """" as "commonCloneBaseURL", this check is a complete no-op on the restore-repo path — "Head.CloneURL" survives unchanged regardless of its value. Every other downloader ("github.go", "gitlab.go", "gitea_downloader.go", "codebase.go", "codecommit.go", "onedev.go") passes a real base URL, so they are not affected. "services/migrations/gitea_uploader.go" then uses the unvalidated value directly: err := g.gitRepo.AddRemote(remote, pr.Head.CloneURL, true) // ... later: fetch from that remote resulting in the server executing "git fetch" against an attacker-controlled URL sourced from the dump file. RCE via git's "ext::" transport helper was tested and ruled out — a normal "git" install rejects it by default ("fatal: transport 'ext' not allowed"), independent of Gitea's own configuration. This report is scoped to SSRF and local git-repository disclosure. Confirmed present, byte-for-byte identical, in "v1.26.4" (latest stable tag), "release/v1.27", and "main", by direct checkout and diff. PoC 1. Create a dump directory following the normal "restore-repo" layout ("repo.yml", etc.), and add a "pull_request.yml" containing at least one entry with: - number: 1 head: cloneURL: "http://<attacker-controlled-or-internal-host>:<port>/ssrf-proof" ref: "main" 2. Run "gitea restore-repo" against that dump directory for any repo owner. 3. Observe on the target host/listener: an actual "git" HTTP discovery request arrives, e.g. "GET /ssrf-proof/info/refs?service=git-upload-pack", driven entirely by the value from the dump file. Verified the core mechanism (steps 2–3, i.e. the unvalidated "Head.CloneURL" surviving "CheckAndEnsureSafePR("")" and then being used in a real "git remote add" + "git fetch") with a minimal, standalone Go program built from the verbatim, unmodified "hasBaseURL" / "CheckAndEnsureSafePR" function bodies (attached: "gitea_ssrf_poc.go"), run end-to-end against a local HTTP listener. The listener's access log confirms the request actually arrives. Impact An attacker who can get an administrator to run "gitea restore-repo" against a malicious dump (the same threat model already accepted for the just-fixed path-traversal issue in this command, #38215) can make the Gitea server issue a "git fetch" against an arbitrary attacker-chosen URL. This allows: - SSRF against internal-only services or cloud metadata endpoints reachable from the Gitea host. - Disclosure of local git repositories reachable via "file://" paths readable by the Gitea process. No public disclosure planned. Happy to provide further detail on request.
Affected Packages
https://github.com/go-gitea/gitea.git (GITHUB):
Affected version(s) >=v0.9.99 <v1.27.0Fix Suggestion:
Update to version v1.27.0gitea.dev (GO):
Affected version(s) >=v0.9.99 <v1.27.0Fix Suggestion:
Update to version v1.27.0Related Resources (3)
Do you need more information?
Contact UsCVSS v4
Base Score:
8.2
Attack Vector
LOCAL
Attack Complexity
LOW
Attack Requirements
NONE
Privileges Required
NONE
User Interaction
PASSIVE
Vulnerable System Confidentiality
HIGH
Vulnerable System Integrity
NONE
Vulnerable System Availability
NONE
Subsequent System Confidentiality
HIGH
Subsequent System Integrity
NONE
Subsequent System Availability
NONE
CVSS v3
Base Score:
6.3
Attack Vector
LOCAL
Attack Complexity
LOW
Privileges Required
NONE
User Interaction
REQUIRED
Scope
CHANGED
Confidentiality
HIGH
Integrity
NONE
Availability
NONE
Weakness Type (CWE)
Server-Side Request Forgery (SSRF)