all repos

mugit @ a49f890766eef21e2989fe28c9347e76ede0add7

馃惍 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
Authored at: 2026-01-23 00:21:32 +0200
Change ID: zorkqmlkxzmsxttskskwuvryrmxvpoko
Parent: a0a9d4a
M go.mod
路路路
        4
        4
         

      
        5
        5
         require (

      
        6
        6
         	github.com/bluekeyes/go-gitdiff v0.8.1

      
        
        7
        +	github.com/cyphar/filepath-securejoin v0.4.1

      
        7
        8
         	github.com/gliderlabs/ssh v0.3.8

      
        8
        9
         	github.com/go-git/go-git/v5 v5.16.4

      
        9
        10
         	github.com/urfave/cli/v3 v3.6.2

      路路路
        19
        20
         	github.com/ProtonMail/go-crypto v1.1.6 // indirect

      
        20
        21
         	github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect

      
        21
        22
         	github.com/cloudflare/circl v1.6.1 // indirect

      
        22
        
        -	github.com/cyphar/filepath-securejoin v0.4.1 // indirect

      
        23
        23
         	github.com/emirpasic/gods v1.18.1 // indirect

      
        24
        24
         	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect

      
        25
        25
         	github.com/go-git/go-billy/v5 v5.6.2 // indirect

      
M internal/handlers/git.go
路路路
        5
        5
         	"io"

      
        6
        6
         	"log/slog"

      
        7
        7
         	"net/http"

      
        8
        
        -	"path/filepath"

      
        9
        8
         

      
        
        9
        +	securejoin "github.com/cyphar/filepath-securejoin"

      
        10
        10
         	"olexsmir.xyz/mugit/internal/git/gitservice"

      
        11
        11
         )

      
        12
        12
         

      路路路
        40
        40
         	w.Header().Set("content-type", "application/x-git-upload-pack-advertisement")

      
        41
        41
         	w.WriteHeader(http.StatusOK)

      
        42
        42
         

      
        43
        
        -	path := filepath.Join(h.c.Repo.Dir, filepath.Clean(name))

      
        
        43
        +	path, err := securejoin.SecureJoin(h.c.Repo.Dir, name)

      
        
        44
        +	if err != nil {

      
        
        45
        +		w.WriteHeader(http.StatusBadRequest)

      
        
        46
        +		slog.Error("git: info/refs", "err", err)

      
        
        47
        +		return

      
        
        48
        +	}

      
        
        49
        +

      
        44
        50
         	if err := gitservice.InfoRefs(path, w); err != nil {

      
        
        51
        +		w.WriteHeader(http.StatusInternalServerError)

      
        45
        52
         		slog.Error("git: info/refs", "err", err)

      
        46
        53
         		return

      
        47
        54
         	}

      路路路
        62
        69
         

      
        63
        70
         	reader := io.Reader(r.Body)

      
        64
        71
         	if r.Header.Get("Content-Encoding") == "gzip" {

      
        65
        
        -		gr, err := gzip.NewReader(r.Body)

      
        66
        
        -		if err != nil {

      
        
        72
        +		gr, gerr := gzip.NewReader(r.Body)

      
        
        73
        +		if gerr != nil {

      
        67
        74
         			w.WriteHeader(http.StatusInternalServerError)

      
        68
        
        -			slog.Error("git: gzip reader", "err", err)

      
        
        75
        +			slog.Error("git: gzip reader", "err", gerr)

      
        69
        76
         			return

      
        70
        77
         		}

      
        71
        78
         		defer gr.Close()

      
        72
        79
         		reader = gr

      
        73
        80
         	}

      
        74
        81
         

      
        75
        
        -	path := filepath.Join(h.c.Repo.Dir, filepath.Clean(name))

      
        
        82
        +	path, err := securejoin.SecureJoin(h.c.Repo.Dir, name)

      
        
        83
        +	if err != nil {

      
        
        84
        +		w.WriteHeader(http.StatusBadRequest)

      
        
        85
        +		slog.Error("git: info/refs", "err", err)

      
        
        86
        +		return

      
        
        87
        +	}

      
        
        88
        +

      
        76
        89
         	if err := gitservice.UploadPack(path, true, reader, newFlushWriter(w)); err != nil {

      
        
        90
        +		w.WriteHeader(http.StatusInternalServerError)

      
        77
        91
         		slog.Error("git: upload-pack", "err", err)

      
        78
        92
         		return

      
        79
        93
         	}

      
M internal/handlers/repo.go
路路路
        15
        15
         	"strings"

      
        16
        16
         	"time"

      
        17
        17
         

      
        
        18
        +	securejoin "github.com/cyphar/filepath-securejoin"

      
        18
        19
         	"github.com/yuin/goldmark"

      
        19
        20
         	"github.com/yuin/goldmark/extension"

      
        20
        21
         	"github.com/yuin/goldmark/renderer/html"

      路路路
        323
        324
         	}

      
        324
        325
         }

      
        325
        326
         

      
        326
        
        -var errPrivateRepo = errors.New("privat err")

      
        
        327
        +var errPrivateRepo = errors.New("private err")

      
        327
        328
         

      
        328
        329
         func (h *handlers) openPublicRepo(name, ref string) (*git.Repo, error) {

      
        329
        
        -	n := filepath.Clean(name)

      
        330
        
        -	repo, err := git.Open(filepath.Join(h.c.Repo.Dir, n), ref)

      
        
        330
        +	path, err := securejoin.SecureJoin(h.c.Repo.Dir, name)

      
        
        331
        +	if err != nil {

      
        
        332
        +		return nil, err

      
        
        333
        +	}

      
        
        334
        +

      
        
        335
        +	repo, err := git.Open(path, ref)

      
        331
        336
         	if err != nil {

      
        332
        337
         		return nil, err

      
        333
        338
         	}

      
M internal/ssh/server.go
路路路
        3
        3
         import (

      
        4
        4
         	"fmt"

      
        5
        5
         	"log/slog"

      
        6
        
        -	"path/filepath"

      
        7
        6
         	"slices"

      
        8
        7
         	"strconv"

      
        9
        8
         

      
        
        9
        +	securejoin "github.com/cyphar/filepath-securejoin"

      
        10
        10
         	"github.com/gliderlabs/ssh"

      
        11
        11
         	"olexsmir.xyz/mugit/internal/config"

      
        12
        12
         	"olexsmir.xyz/mugit/internal/git"

      路路路
        41
        41
         		Handler:          s.handler,

      
        42
        42
         		PublicKeyHandler: s.authhandler,

      
        43
        43
         	}

      
        44
        
        -	srv.SetOption(ssh.HostKeyFile(s.c.SSH.HostKey)) // TODO: validate `gossh.ParsePrivateKey`

      
        
        44
        +

      
        
        45
        +	if err := srv.SetOption(ssh.HostKeyFile(s.c.SSH.HostKey)); err != nil {

      
        
        46
        +		// TODO: validate `gossh.ParsePrivateKey`

      
        
        47
        +		return err

      
        
        48
        +	}

      
        
        49
        +

      
        45
        50
         	return srv.ListenAndServe()

      
        46
        51
         }

      
        47
        52
         

      路路路
        53
        58
         	}

      
        54
        59
         

      
        55
        60
         	slog.Info("ssh request", "fingerprint", fingerprint)

      
        56
        
        -

      
        57
        61
         	authorized := slices.ContainsFunc(s.authKeys, func(i gossh.PublicKey) bool {

      
        58
        62
         		return ssh.KeysEqual(key, i)

      
        59
        63
         	})

      路路路
        73
        77
         

      
        74
        78
         	gitCmd := cmd[0]

      
        75
        79
         	repoPath := cmd[1]

      
        
        80
        +	repoPath, err := securejoin.SecureJoin(s.c.Repo.Dir, repoPath)

      
        
        81
        +	if err != nil {

      
        
        82
        +		slog.Error("ssh: invalid path", "err", err)

      
        
        83
        +		s.repoNotFound(sess)

      
        
        84
        +		return

      
        
        85
        +	}

      
        76
        86
         

      
        77
        
        -	repoPath = filepath.Join(s.c.Repo.Dir, filepath.Clean(repoPath))

      
        78
        87
         	repo, err := git.Open(repoPath, "")

      
        79
        88
         	if err != nil {

      
        80
        89
         		slog.Error("ssh: failed to open repo", "err", err)