Summary
Gitea: Repository Migration Follows Git HTTP Redirects After URL Allow/Block Validation, Enabling Internal Git Repository Exfiltration
Advisory details
Repository Migration Follows Git HTTP Redirects After URL Allow/Block Validation, Enabling Internal Git Repository Exfiltration
Summary
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.
Technical Root Cause Analysis
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:180parses the submitted web migration clone address.C:\Users\One\Desktop\gitea_new\gitea\routers\web\repo\migrate.go:182callsmigrations.IsMigrateURLAllowed.C:\Users\One\Desktop\gitea_new\gitea\routers\api\v1\repo\migrate.go:101parses the submitted API migration clone address.C:\Users\One\Desktop\gitea_new\gitea\routers\api\v1\repo\migrate.go:103callsmigrations.IsMigrateURLAllowed.
The URL validator resolves and checks the initially supplied host:
C:\Users\One\Desktop\gitea_new\gitea\services\migrations\migrate.go:44definesIsMigrateURLAllowed.C:\Users\One\Desktop\gitea_new\gitea\services\migrations\migrate.go:75rejects unsupported schemes.C:\Users\One\Desktop\gitea_new\gitea\services\migrations\migrate.go:79extracts the host.C:\Users\One\Desktop\gitea_new\gitea\services\migrations\migrate.go:85performs DNS resolution.C:\Users\One\Desktop\gitea_new\gitea\services\migrations\migrate.go:86enforces 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:93callsgitrepo.CloneExternalRepowithopts.CloneAddr.C:\Users\One\Desktop\gitea_new\gitea\modules\gitrepo\clone.go:13delegates togit.Clone.C:\Users\One\Desktop\gitea_new\gitea\modules\git\repo.go:123constructs agit clonecommand.C:\Users\One\Desktop\gitea_new\gitea\modules\git\repo.go:125conditionally sets onlyhttp.sslVerify=false.C:\Users\One\Desktop\gitea_new\gitea\modules\git\repo.go:154appends 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:16definesNewMigrationHTTPClient.C:\Users\One\Desktop\gitea_new\gitea\services\migrations\http_client.go:27applies 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:177creates a mirror record when migration is requested as a mirror.C:\Users\One\Desktop\gitea_new\gitea\services\repository\migrate.go:188stores the sanitized original remote address.C:\Users\One\Desktop\gitea_new\gitea\services\mirror\mirror_pull.go:120later performs mirror synchronization withgit fetch --tags.C:\Users\One\Desktop\gitea_new\gitea\services\mirror\mirror_pull.go:126fetches 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 Assets & Attack Surface
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:
- Repository migrations are enabled, which is the default in code and example configuration (
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). - The attacker has a low-privileged account or the instance permits self-registration. Default/example service settings do not force registration confirmation by default.
- For persistent exfiltration, pull mirrors must be enabled (
C:\Users\One\Desktop\gitea_new\gitea\modules\setting\mirror.go:20). - The Gitea server can reach internal Git HTTP(S) services that the attacker cannot reach directly.
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.
Exploitation Walkthrough
Scenario 1: One-time internal repository exfiltration
- The attacker obtains a low-privileged Gitea account. On instances with open registration, this may require only creating a user.
- The attacker hosts a public HTTP endpoint that appears to be a Git remote, for example
https://attacker.example/public.git. - The attacker configures the endpoint to respond to the initial Git discovery request with an HTTP redirect to an internal target, for example `http://internal-git.company.local/team/private.git/i
References
Related vulnerabilities
All Supply chain →- CRITICALCVE-2026-75856
CodeWhale: SSRF bypass - TOCTOU on DNS failure for DNS pinning
- CRITICALCVE-2026-71428
unstructured: Server-Side Request Forgery in the URL-based partitioning
- HIGHCVE-2026-65842
Plate: SSRF with response disclosure in DOCX image embedding
- HIGHCVE-2026-61704
link-preview-js DNS Rebinding SSRF Bypass / Incomplete Fix for CVE-2026-43897
- HIGHCVE-2026-75975
fast-uri vulnerable to server-side request forgery via malformed IPv6 normalization
- HIGHCVE-2026-75899
fast-uri vulnerable to server-side request forgery via repeated hostname percent-decoding