all repos

mugit @ a49f890

🐮 git server that your cow will love
4 files changed, 42 insertions(+), 14 deletions(-)
security improvments
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-01-23 01:02:33 +0200
Change ID: zorkqmlkxzmsxttskskwuvryrmxvpoko
Parent: a0a9d4a
M go.mod

@@ -4,6 +4,7 @@ go 1.25.3

require ( github.com/bluekeyes/go-gitdiff v0.8.1 + github.com/cyphar/filepath-securejoin v0.4.1 github.com/gliderlabs/ssh v0.3.8 github.com/go-git/go-git/v5 v5.16.4 github.com/urfave/cli/v3 v3.6.2

@@ -19,7 +20,6 @@ github.com/Microsoft/go-winio v0.6.2 // indirect

github.com/ProtonMail/go-crypto v1.1.6 // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/cloudflare/circl v1.6.1 // indirect - github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect
M internal/handlers/git.go

@@ -5,8 +5,8 @@ "compress/gzip"

"io" "log/slog" "net/http" - "path/filepath" + securejoin "github.com/cyphar/filepath-securejoin" "olexsmir.xyz/mugit/internal/git/gitservice" )

@@ -40,8 +40,15 @@

w.Header().Set("content-type", "application/x-git-upload-pack-advertisement") w.WriteHeader(http.StatusOK) - path := filepath.Join(h.c.Repo.Dir, filepath.Clean(name)) + path, err := securejoin.SecureJoin(h.c.Repo.Dir, name) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + slog.Error("git: info/refs", "err", err) + return + } + if err := gitservice.InfoRefs(path, w); err != nil { + w.WriteHeader(http.StatusInternalServerError) slog.Error("git: info/refs", "err", err) return }

@@ -62,18 +69,25 @@ w.WriteHeader(http.StatusOK)

reader := io.Reader(r.Body) if r.Header.Get("Content-Encoding") == "gzip" { - gr, err := gzip.NewReader(r.Body) - if err != nil { + gr, gerr := gzip.NewReader(r.Body) + if gerr != nil { w.WriteHeader(http.StatusInternalServerError) - slog.Error("git: gzip reader", "err", err) + slog.Error("git: gzip reader", "err", gerr) return } defer gr.Close() reader = gr } - path := filepath.Join(h.c.Repo.Dir, filepath.Clean(name)) + path, err := securejoin.SecureJoin(h.c.Repo.Dir, name) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + slog.Error("git: info/refs", "err", err) + return + } + if err := gitservice.UploadPack(path, true, reader, newFlushWriter(w)); err != nil { + w.WriteHeader(http.StatusInternalServerError) slog.Error("git: upload-pack", "err", err) return }
M internal/handlers/repo.go

@@ -15,6 +15,7 @@ "strconv"

"strings" "time" + securejoin "github.com/cyphar/filepath-securejoin" "github.com/yuin/goldmark" "github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/renderer/html"

@@ -323,11 +324,15 @@ }

} } -var errPrivateRepo = errors.New("privat err") +var errPrivateRepo = errors.New("private err") func (h *handlers) openPublicRepo(name, ref string) (*git.Repo, error) { - n := filepath.Clean(name) - repo, err := git.Open(filepath.Join(h.c.Repo.Dir, n), ref) + path, err := securejoin.SecureJoin(h.c.Repo.Dir, name) + if err != nil { + return nil, err + } + + repo, err := git.Open(path, ref) if err != nil { return nil, err }
M internal/ssh/server.go

@@ -3,10 +3,10 @@

import ( "fmt" "log/slog" - "path/filepath" "slices" "strconv" + securejoin "github.com/cyphar/filepath-securejoin" "github.com/gliderlabs/ssh" "olexsmir.xyz/mugit/internal/config" "olexsmir.xyz/mugit/internal/git"

@@ -41,7 +41,12 @@ Addr: ":" + strconv.Itoa(s.c.SSH.Port),

Handler: s.handler, PublicKeyHandler: s.authhandler, } - srv.SetOption(ssh.HostKeyFile(s.c.SSH.HostKey)) // TODO: validate `gossh.ParsePrivateKey` + + if err := srv.SetOption(ssh.HostKeyFile(s.c.SSH.HostKey)); err != nil { + // TODO: validate `gossh.ParsePrivateKey` + return err + } + return srv.ListenAndServe() }

@@ -53,7 +58,6 @@ return false

} slog.Info("ssh request", "fingerprint", fingerprint) - authorized := slices.ContainsFunc(s.authKeys, func(i gossh.PublicKey) bool { return ssh.KeysEqual(key, i) })

@@ -73,8 +77,13 @@ }

gitCmd := cmd[0] repoPath := cmd[1] + repoPath, err := securejoin.SecureJoin(s.c.Repo.Dir, repoPath) + if err != nil { + slog.Error("ssh: invalid path", "err", err) + s.repoNotFound(sess) + return + } - repoPath = filepath.Join(s.c.Repo.Dir, filepath.Clean(repoPath)) repo, err := git.Open(repoPath, "") if err != nil { slog.Error("ssh: failed to open repo", "err", err)