From 620b9b7e2c3942a161f467b6788f022381f9110f Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Sat, 14 Mar 2026 15:02:25 +0200 Subject: [PATCH] Fix path traversal in /store endpoint Validate that the hash URL parameter matches the expected SHA1 hex format (40 lowercase hex characters) before using it in file path operations. Signed-off-by: Stefan Prodan --- pkg/api/http/store.go | 7 +++++++ pkg/api/http/store_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/pkg/api/http/store.go b/pkg/api/http/store.go index 1a85f54..f959be2 100644 --- a/pkg/api/http/store.go +++ b/pkg/api/http/store.go @@ -7,11 +7,14 @@ import ( "net/http" "os" "path" + "regexp" "github.com/gorilla/mux" "go.uber.org/zap" ) +var validHash = regexp.MustCompile(`^[a-f0-9]{40}$`) + // Store godoc // @Summary Upload file // @Description writes the posted content to disk at /data/hash and returns the SHA1 hash of the content @@ -54,6 +57,10 @@ func (s *Server) storeReadHandler(w http.ResponseWriter, r *http.Request) { defer span.End() hash := mux.Vars(r)["hash"] + if !validHash.MatchString(hash) { + s.ErrorResponse(w, r, span, "invalid hash", http.StatusBadRequest) + return + } content, err := os.ReadFile(path.Join(s.config.DataPath, hash)) if err != nil { s.logger.Warn("reading file failed", zap.Error(err), zap.String("file", path.Join(s.config.DataPath, hash))) diff --git a/pkg/api/http/store_test.go b/pkg/api/http/store_test.go index ab52289..ba18e97 100644 --- a/pkg/api/http/store_test.go +++ b/pkg/api/http/store_test.go @@ -52,3 +52,31 @@ func TestStoreReadHandler_ContentType(t *testing.T) { } } } + +func TestStoreReadHandler_PathTraversal(t *testing.T) { + srv := NewMockServer() + srv.config.DataPath = t.TempDir() + + traversalPaths := []string{ + "../../../../etc/passwd", + "../../../etc/shadow", + "..%2f..%2f..%2fetc%2fpasswd", + "abc123", + "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzg", // 40 chars but not hex + } + + for _, tp := range traversalPaths { + req, err := http.NewRequest("GET", "/store/"+tp, nil) + if err != nil { + t.Fatal(err) + } + req = mux.SetURLVars(req, map[string]string{"hash": tp}) + + rr := httptest.NewRecorder() + http.HandlerFunc(srv.storeReadHandler).ServeHTTP(rr, req) + + if !strings.Contains(rr.Body.String(), "invalid hash") { + t.Errorf("path %q: expected 'invalid hash' error, got %q", tp, rr.Body.String()) + } + } +}