From 9731ad7c3c4cf29f169afc0e1d4c475188975539 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 20 Jul 2026 23:21:17 +0800 Subject: [PATCH] fix: revert git clone http redirection forbidden (#38530) (#38545) backport #38530 --- modules/git/redirection.go | 15 +++++++++++++++ modules/git/repo.go | 4 +--- modules/git/repo_test.go | 1 + services/mirror/mirror_pull.go | 14 +++++++------- 4 files changed, 24 insertions(+), 10 deletions(-) create mode 100644 modules/git/redirection.go diff --git a/modules/git/redirection.go b/modules/git/redirection.go new file mode 100644 index 00000000000..ee7a365482d --- /dev/null +++ b/modules/git/redirection.go @@ -0,0 +1,15 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import "gitea.dev/modules/git/gitcmd" + +func HandleGitCmdHTTPRedirection(cmd *gitcmd.Command, targets ...string) { + // Protect from SSRF vector (e.g. migrating from an attacker URL). + // cmd.AddConfig("http.followRedirects", "false") + // However, we can't do so at the moment: + // this fails due to 301: git -c http.followRedirects=false clone -v https://gitlab.com/{owner}/{repo} + // this succeeds: git -c http.followRedirects=false clone -v https://gitlab.com/{owner}/{repo}.git + // FIXME: GIT-CLONE-HTTP-REDIRECT-SSRF: need a complete solution in the future +} diff --git a/modules/git/repo.go b/modules/git/repo.go index 289033332bf..a5b1710540b 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -121,9 +121,7 @@ func Clone(ctx context.Context, from, to string, opts CloneRepoOptions) error { } cmd := gitcmd.NewCommand().AddArguments("clone") - // Never follow HTTP redirects: no clone caller needs them, and a remote redirecting to an - // otherwise-blocked address would be an SSRF vector (e.g. migrating from an attacker URL). - cmd.AddArguments("-c", "http.followRedirects=false") + HandleGitCmdHTTPRedirection(cmd, from, to) if opts.SkipTLSVerify { cmd.AddArguments("-c", "http.sslVerify=false") } diff --git a/modules/git/repo_test.go b/modules/git/repo_test.go index be0a21a83db..4122be56638 100644 --- a/modules/git/repo_test.go +++ b/modules/git/repo_test.go @@ -26,6 +26,7 @@ func TestRepoIsEmpty(t *testing.T) { // TestCloneRefusesRedirects ensures Clone never follows HTTP redirects, so a remote // cannot redirect to an otherwise-blocked address (SSRF, e.g. during migration). func TestCloneRefusesRedirects(t *testing.T) { + t.Skip("FIXME: GIT-CLONE-HTTP-REDIRECT-SSRF: need a complete solution in the future") var targetHit atomic.Bool target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { targetHit.Store(true) diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index 9a861e4a664..d0aad922990 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -72,7 +72,8 @@ func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error func pruneBrokenReferences(ctx context.Context, m *repo_model.Mirror, gitRepo gitrepo.Repository, timeout time.Duration) error { // Never follow HTTP redirects, see cmdFetch in runSync. - cmd := gitcmd.NewCommand("remote", "prune").AddConfig("http.followRedirects", "false").AddDynamicArguments(m.GetRemoteName()).WithTimeout(timeout) + cmd := gitcmd.NewCommand("remote", "prune").AddDynamicArguments(m.GetRemoteName()).WithTimeout(timeout) + git.HandleGitCmdHTTPRedirection(cmd, m.GetRemoteName()) stdout, _, pruneErr := gitrepo.RunCmdString(ctx, gitRepo, cmd) if pruneErr != nil { // sanitize the output, since it may contain the remote address, which may contain a password @@ -129,9 +130,8 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*repo_module.SyncResu // use fetch but not remote update because git fetch support --tags but remote update doesn't cmdFetch := func() *gitcmd.Command { - // Never follow HTTP redirects: a mirror remote that later starts redirecting to an - // otherwise-blocked address would be an SSRF/exfiltration vector on scheduled syncs. - cmd := gitcmd.NewCommand("fetch", "--tags").AddConfig("http.followRedirects", "false") + cmd := gitcmd.NewCommand("fetch", "--tags") + git.HandleGitCmdHTTPRedirection(cmd, m.GetRemoteName()) if m.EnablePrune { cmd.AddArguments("--prune") } @@ -212,9 +212,9 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*repo_module.SyncResu } cmdRemoteUpdatePrune := func() *gitcmd.Command { - // Never follow HTTP redirects, see cmdFetch above. - return gitcmd.NewCommand("remote", "update", "--prune").AddConfig("http.followRedirects", "false"). - AddDynamicArguments(m.GetRemoteName()).WithTimeout(timeout).WithEnv(envs) + cmd := gitcmd.NewCommand("remote", "update", "--prune").AddDynamicArguments(m.GetRemoteName()).WithTimeout(timeout).WithEnv(envs) + git.HandleGitCmdHTTPRedirection(cmd, m.GetRemoteName()) + return cmd } if repo_service.HasWiki(ctx, m.Repo) {