From 7b423ed1efce61341c08acb9c66a767df2e61e52 Mon Sep 17 00:00:00 2001
From: dadav <33197631+dadav@users.noreply.github.com>
Date: Fri, 8 Mar 2024 20:46:58 +0100
Subject: [PATCH] fix: Fix path related security issues

---
 internal/v3/api/module.go         | 12 ++++++++++++
 internal/v3/api/release.go        | 11 +++++++++++
 internal/v3/backend/filesystem.go |  9 +++++++++
 internal/v3/utils/path.go         | 13 +++++++++++++
 4 files changed, 45 insertions(+)
 create mode 100644 internal/v3/utils/path.go

diff --git a/internal/v3/api/module.go b/internal/v3/api/module.go
index 7c7c60a..43ec9ec 100644
--- a/internal/v3/api/module.go
+++ b/internal/v3/api/module.go
@@ -11,6 +11,7 @@ import (
 
 	"github.com/dadav/gorge/internal/log"
 	"github.com/dadav/gorge/internal/v3/backend"
+	"github.com/dadav/gorge/internal/v3/utils"
 	gen "github.com/dadav/gorge/pkg/gen/v3/openapi"
 )
 
@@ -29,6 +30,17 @@ type DeleteModule500Response struct {
 
 // DeleteModule - Delete module
 func (s *ModuleOperationsApi) DeleteModule(ctx context.Context, moduleSlug string, reason string) (gen.ImplResponse, error) {
+	if !utils.CheckModuleSlug(moduleSlug) {
+		err := errors.New("invalid module slug")
+		return gen.Response(
+			400,
+			DeleteModule500Response{
+				Message: err.Error(),
+				Errors:  []string{err.Error()},
+			},
+		), nil
+	}
+
 	err := backend.ConfiguredBackend.DeleteModuleBySlug(moduleSlug)
 	if err == nil {
 		return gen.Response(204, nil), nil
diff --git a/internal/v3/api/release.go b/internal/v3/api/release.go
index 82b9a6f..040f8b8 100644
--- a/internal/v3/api/release.go
+++ b/internal/v3/api/release.go
@@ -14,6 +14,7 @@ import (
 
 	"github.com/dadav/gorge/internal/config"
 	"github.com/dadav/gorge/internal/v3/backend"
+	"github.com/dadav/gorge/internal/v3/utils"
 	gen "github.com/dadav/gorge/pkg/gen/v3/openapi"
 )
 
@@ -65,6 +66,16 @@ type DeleteRelease500Response struct {
 
 // DeleteRelease - Delete module release
 func (s *ReleaseOperationsApi) DeleteRelease(ctx context.Context, releaseSlug string, reason string) (gen.ImplResponse, error) {
+	if !utils.CheckReleaseSlug(releaseSlug) {
+		err := errors.New("invalid release slug")
+		return gen.Response(
+			400,
+			DeleteRelease500Response{
+				Message: err.Error(),
+				Errors:  []string{err.Error()},
+			},
+		), nil
+	}
 	err := backend.ConfiguredBackend.DeleteReleaseBySlug(releaseSlug)
 	if err == nil {
 		return gen.Response(204, nil), nil
diff --git a/internal/v3/backend/filesystem.go b/internal/v3/backend/filesystem.go
index 166d180..0a90f33 100644
--- a/internal/v3/backend/filesystem.go
+++ b/internal/v3/backend/filesystem.go
@@ -19,6 +19,7 @@ import (
 	"github.com/dadav/gorge/internal/config"
 	"github.com/dadav/gorge/internal/log"
 	"github.com/dadav/gorge/internal/model"
+	"github.com/dadav/gorge/internal/v3/utils"
 	gen "github.com/dadav/gorge/pkg/gen/v3/openapi"
 	"golang.org/x/mod/semver"
 )
@@ -139,7 +140,12 @@ func (s *FilesystemBackend) AddRelease(releaseData []byte) (*gen.Release, error)
 	if err != nil {
 		return nil, err
 	}
+
 	releaseSlug := fmt.Sprintf("%s-%s", metadata.Name, metadata.Version)
+	if !utils.CheckReleaseSlug(releaseSlug) {
+		return nil, errors.New("invalid release slug")
+	}
+
 	if _, ok := s.Releases[releaseSlug]; ok {
 		return nil, errors.New("release already exist")
 	}
@@ -428,6 +434,9 @@ func ReadReleaseMetadataFromBytes(data []byte) (*model.ReleaseMetadata, string,
 				return nil, readme.String(), err
 			}
 
+			if !utils.CheckModuleSlug(releaseMetadata.Name) {
+				return nil, readme.String(), errors.New("invalid module name")
+			}
 		case "README.md":
 			_, err = io.Copy(readme, tarReader)
 			if err != nil {
diff --git a/internal/v3/utils/path.go b/internal/v3/utils/path.go
new file mode 100644
index 0000000..36390ae
--- /dev/null
+++ b/internal/v3/utils/path.go
@@ -0,0 +1,13 @@
+package utils
+
+import "regexp"
+
+func CheckModuleSlug(slug string) bool {
+	r, _ := regexp.Compile(`^[a-zA-Z0-9]+[-\/][a-z][a-z0-9_]*$`)
+	return r.MatchString(slug)
+}
+
+func CheckReleaseSlug(slug string) bool {
+	r, _ := regexp.Compile(`^[a-zA-Z0-9]+[-\/][a-z][a-z0-9_]*[-\/][0-9]+\.[0-9]+\.[0-9]+(?:[\-+].+)?$`)
+	return r.MatchString(slug)
+}