From ca454e925b8131f54e9d5250ef3ecaee9f40c108 Mon Sep 17 00:00:00 2001 From: tjh Date: Wed, 14 May 2025 10:16:19 +0100 Subject: [PATCH] knotserver: reject 'git-receive-pack' service in info/refs * Checks service name query parameter in info/refs handler; rejects any service request that doesn't match 'git-upload-pack'. * Produces 'text/plain' error responses so the git client will display them. * Adds a specific reject message for 'git-receive-pack'. This will produce a less cryptic error in the terminal when trying to push over http. --- knotserver/git.go | 53 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/knotserver/git.go b/knotserver/git.go index c609ad75..d6dc50ad 100644 --- a/knotserver/git.go +++ b/knotserver/git.go @@ -2,6 +2,7 @@ package knotserver import ( "compress/gzip" + "fmt" "io" "net/http" "path/filepath" @@ -14,20 +15,40 @@ import ( func (d *Handle) InfoRefs(w http.ResponseWriter, r *http.Request) { did := chi.URLParam(r, "did") name := chi.URLParam(r, "name") - repo, _ := securejoin.SecureJoin(d.c.Repo.ScanPath, filepath.Join(did, name)) + repoName, err := securejoin.SecureJoin(did, name) + if err != nil { + gitError(w, "repository not found", http.StatusNotFound) + d.l.Error("git: failed to secure join repo path", "handler", "InfoRefs", "error", err) + return + } - w.Header().Set("content-type", "application/x-git-upload-pack-advertisement") - w.WriteHeader(http.StatusOK) + repoPath, err := securejoin.SecureJoin(d.c.Repo.ScanPath, repoName) + if err != nil { + gitError(w, "repository not found", http.StatusNotFound) + d.l.Error("git: failed to secure join repo path", "handler", "InfoRefs", "error", err) + return + } cmd := service.ServiceCommand{ - Dir: repo, + Dir: repoPath, Stdout: w, } - if err := cmd.InfoRefs(); err != nil { - writeError(w, err.Error(), 500) - d.l.Error("git: failed to execute git-upload-pack (info/refs)", "handler", "InfoRefs", "error", err) - return + serviceName := r.URL.Query().Get("service") + switch serviceName { + case "git-upload-pack": + w.Header().Set("content-type", "application/x-git-upload-pack-advertisement") + w.WriteHeader(http.StatusOK) + + if err := cmd.InfoRefs(); err != nil { + gitError(w, err.Error(), http.StatusInternalServerError) + d.l.Error("git: process failed", "handler", "InfoRefs", "service", serviceName, "error", err) + return + } + case "git-receive-pack": + d.RejectPush(w, r, name) + default: + gitError(w, fmt.Sprintf("service unsupported: '%s'", serviceName), http.StatusForbidden) } } @@ -71,3 +92,19 @@ func (d *Handle) UploadPack(w http.ResponseWriter, r *http.Request) { return } } + +func (d *Handle) RejectPush(w http.ResponseWriter, r *http.Request, unqualifiedRepoName string) { + // A text/plain response will cause git to print each line of the body + // prefixed with "remote: ". + w.Header().Set("content-type", "text/plain; charset=UTF-8") + w.WriteHeader(http.StatusForbidden) + + fmt.Fprintf(w, "Welcome to Tangled.sh!\n\nPushes are currently only supported over SSH.") + fmt.Fprintf(w, "\n\n") +} + +func gitError(w http.ResponseWriter, msg string, status int) { + w.Header().Set("content-type", "text/plain; charset=UTF-8") + w.WriteHeader(status) + fmt.Fprintf(w, "%s\n", msg) +} -- 2.51.2