Go · code.gitea.io/gitea
Gitea: Repository Migration Follows Git HTTP Redirects After URL Allow/Block Validation, Enabling Internal Git Repository Exfiltration
Gitea validates the user-supplied repository migration URL, but the actual clone and later mirror fetch operations are performed by the Git command-line client without disabling HTTP redirects. Git's default http.followRedirects=initial follows the first redirect and then uses the redirected URL as the base for later repository object requests.
This creates a URL-policy bypass, SSRF, and repository exfiltration primitive. A low-privileged authenticated user can submit an allowed public Git URL that redirects the Gitea server to an otherwise blocked or internal Git HTTP(S) endpoint. Local validation confirmed that Git followed a first-hop redirect to 127.0.0.1, fetched Git objects, and completed the clone; git -c http.followRedirects=false clone blocked the same path.
The main impact is internal Git repository exfiltration into an attacker-controlled Gitea repository. Pull mirrors increase risk because scheduled git fetch --tags operations can keep following the redirect and collect future internal commits.
This is High severity by default for internet-accessible instances with migrations enabled, and can become Critical where internal repositories contain deploy keys, CI/CD secrets, cloud credentials, Terraform state, kubeconfigs, signing material, or production configuration. Direct unauthenticated exploitation, direct Gitea server RCE, arbitrary file:// import, and default Actions runner execution are not confirmed.
The root cause is a validation/enforcement mismatch across a trust boundary. This should be treated as a product security issue rather than a pure deployment misconfiguration: deployment choices influence reachability and impact, but Gitea applies policy to the originally submitted URL while the Git subprocess is allowed to reach a different effective URL after redirection.
Gitea validates the original migration URL:
C:\Users\One\Desktop\gitea_new\gitea\routers\web\repo\migrate.go:180 parses the submitted web migration clone address.C:\Users\One\Desktop\gitea_new\gitea\routers\web\repo\migrate.go:182 calls migrations.IsMigrateURLAllowed.C:\Users\One\Desktop\gitea_new\gitea\routers\api\v1\repo\migrate.go:101 parses the submitted API migration clone address.C:\Users\One\Desktop\gitea_new\gitea\routers\api\v1\repo\migrate.go:103 calls migrations.IsMigrateURLAllowed.The URL validator resolves and checks the initially supplied host:
C:\Users\One\Desktop\gitea_new\gitea\services\migrations\migrate.go:44 defines IsMigrateURLAllowed.C:\Users\One\Desktop\gitea_new\gitea\services\migrations\migrate.go:75 rejects unsupported schemes.C:\Users\One\Desktop\gitea_new\gitea\services\migrations\migrate.go:79 extracts the host.C:\Users\One\Desktop\gitea_new\gitea\services\migrations\migrate.go:85 performs DNS resolution.C:\Users\One\Desktop\gitea_new\gitea\services\migrations\migrate.go:86 enforces the allow/block list on the originally resolved host.After this validation, the actual repository data transfer is delegated to Git:
C:\Users\One\Desktop\gitea_new\gitea\services\repository\migrate.go:93 calls gitrepo.CloneExternalRepo with opts.CloneAddr.C:\Users\One\Desktop\gitea_new\gitea\modules\gitrepo\clone.go:13 delegates to git.Clone.C:\Users\One\Desktop\gitea_new\gitea\modules\git\repo.go:123 constructs a git clone command.C:\Users\One\Desktop\gitea_new\gitea\modules\git\repo.go:125 conditionally sets only http.sslVerify=false.C:\Users\One\Desktop\gitea_new\gitea\modules\git\repo.go:154 appends the source and destination arguments.No equivalent network policy is applied to the final URL reached by Git after HTTP redirection. The protected migration HTTP client exists for service-specific migration API calls, but it is not used for the raw Git clone/fetch operation:
C:\Users\One\Desktop\gitea_new\gitea\services\migrations\http_client.go:16 defines NewMigrationHTTPClient.C:\Users\One\Desktop\gitea_new\gitea\services\migrations\http_client.go:27 applies a host-matching dialer.That protection does not wrap git clone or git fetch.
The behavior depends on Git's documented redirect handling. The current Git documentation states that http.followRedirects=initial follows the initial request redirect and uses the redirected URL as the base for follow-up requests. The default value is initial: https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpfollowRedirects
This means the effective enforcement point is not the same as the actual network sink. Gitea checks https://attacker.example/repo.git; Git later retrieves http://127.0.0.1:PORT/internal.git/... or another internal target selected by the redirector.
For mirrors, the issue extends beyond initial migration:
C:\Users\One\Desktop\gitea_new\gitea\services\repository\migrate.go:177 creates a mirror record when migration is requested as a mirror.C:\Users\One\Desktop\gitea_new\gitea\services\repository\migrate.go:188 stores the sanitized original remote address.C:\Users\One\Desktop\gitea_new\gitea\services\mirror\mirror_pull.go:120 later performs mirror synchronization with git fetch --tags.C:\Users\One\Desktop\gitea_new\gitea\services\mirror\mirror_pull.go:126 fetches from the configured remote.Because the mirror sync path also delegates to Git, a redirecting remote can continue to redirect scheduled fetches into an internal target.
Affected features are URL-based repository migration, API repository migration, pull mirror creation through migration, and scheduled pull mirror synchronization. The relevant entrypoints are POST /repo/migrate for signed-in web users (C:\Users\One\Desktop\gitea_new\gitea\routers\web\web.go:1062) and POST /api/v1/repos/migrate for API-token users (C:\Users\One\Desktop\gitea_new\gitea\routers\api\v1\api.go:1170).
Important preconditions are:
C:\Users\One\Desktop\gitea_new\gitea\modules\setting\repository.go:181, C:\Users\One\Desktop\gitea_new\gitea\custom\conf\app.example.ini:1078).C:\Users\One\Desktop\gitea_new\gitea\modules\setting\mirror.go:20).High-value targets include internal Gitea, GitLab, GitHub Enterprise, Bitbucket, cgit, git-http-backend, and static bare Git repositories, especially repositories containing CI/CD definitions, deployment manifests, IaC, credentials, or production configuration.
The key trust boundary is that low-privileged user input influences server-side Git network access. Gitea validates the initially supplied URL, then hands control to a Git subprocess whose redirected destination is not constrained by the same allow/block policy. An attacker can discover exposure by using a controlled redirector, observing outbound Git requests, and testing whether scheduled mirror requests continue to arrive.
https://attacker.example/public.git.Is your project exposed to this? Stateward checks every dependency on every pull request and flags it only if your code actually reaches it.
Check my repoSources: CISA KEV (public domain), OSV.dev & GitHub Advisory Database (CC-BY-4.0), FIRST EPSS, NVD/CWE (public domain). Served live from the Stateward advisory database.