bf0f7bb23f6d81ff3244e640dbbde7fbe53b654d

Author
Kieran Klukas <kieran@dunkirk.sh>
Committer
Kieran Klukas <kieran@dunkirk.sh>
Date

Message

fix: stop failed logins and stray requests from creating repositories

Over HTTP, pushing to a name that does not exist creates the repository.
Two things let that happen when it should not. A request with a wrong
password still created the repository on servers that allow anonymous
writes, because the login failure was forgotten before the check. And
any request carrying service=git-receive-pack in its query string could
create one, including plain file downloads.

Wrong credentials now get 403 before anything is created, and only the
push endpoints create repositories.

💘 Generated with Crush

Assisted-by: Crush:claude-opus-5-5

Diff

 1diff --git a/pkg/web/git.go b/pkg/web/git.go
 2index 5083d83c02d61abea32aeb2cdfc3d1e1460468c9..6f6d73469e80d3248c0546d4a7bef2652ed6e1f8 100644
 3--- a/pkg/web/git.go
 4+++ b/pkg/web/git.go
 5@@ -347,17 +347,24 @@ func withAccess(next http.Handler) http.HandlerFunc {
 6 			}
 7 
 8 		case service == git.ReceivePackService:
 9+			if badCredentials(err) {
10+				renderForbidden(w, r)
11+				return
12+			}
13+
14 			if accessLevel < access.ReadWriteAccess {
15 				askCredentials(w, r)
16 				renderUnauthorized(w, r)
17 				return
18 			}
19 
20-			// Create the repo if it doesn't exist.
21-			if repo == nil {
22-				repo, err = be.CreateRepository(ctx, repoName, user, proto.RepositoryOptions{})
23-				if err != nil {
24-					logger.Error("failed to create repository", "repo", repoName, "err", err)
25+			// Create the repo if it doesn't exist, but only from the push
26+			// endpoints, not from any route that carries the service param.
27+			if repo == nil && (file == "info/refs" || file == git.ReceivePackService.String()) {
28+				var cerr error
29+				repo, cerr = be.CreateRepository(ctx, repoName, user, proto.RepositoryOptions{})
30+				if cerr != nil {
31+					logger.Error("failed to create repository", "repo", repoName, "err", cerr)
32 					renderInternalServerError(w, r)
33 					return
34 				}
35diff --git a/testscript/testdata/http-push-create.txtar b/testscript/testdata/http-push-create.txtar
36new file mode 100644
37index 0000000000000000000000000000000000000000..444868a0059a32ceb6d0f84f3931d677c65a61b6
38--- /dev/null
39+++ b/testscript/testdata/http-push-create.txtar
40@@ -0,0 +1,29 @@
41+# vi: set ft=conf
42+
43+# FIXME: don't skip windows
44+[windows] skip 'curl makes github actions hang'
45+
46+# start soft serve
47+exec soft serve &
48+# wait for SSH server to start
49+ensureserverrunning SSH_PORT
50+
51+soft settings anon-access read-write
52+
53+# bad credentials never create a repository
54+curl http://bob:wrong@localhost:$HTTP_PORT/ghost1.git/info/refs?service=git-receive-pack
55+stdout '403.*'
56+
57+# only the push endpoints create repositories
58+curl http://localhost:$HTTP_PORT/ghost2.git/HEAD?service=git-receive-pack
59+
60+# the push advertisement still creates one
61+curl http://localhost:$HTTP_PORT/ghost3.git/info/refs?service=git-receive-pack
62+soft repo list
63+stdout 'ghost3'
64+! stdout 'ghost1'
65+! stdout 'ghost2'
66+
67+# stop the server
68+[windows] stopserver
69+[windows] ! stderr .