diff --git a/routers/api/packages/generic/generic.go b/routers/api/packages/generic/generic.go
index b65870a8d0..8232931134 100644
--- a/routers/api/packages/generic/generic.go
+++ b/routers/api/packages/generic/generic.go
@@ -8,6 +8,7 @@ import (
 	"net/http"
 	"regexp"
 	"strings"
+	"unicode"
 
 	packages_model "code.gitea.io/gitea/models/packages"
 	"code.gitea.io/gitea/modules/log"
@@ -18,8 +19,8 @@ import (
 )
 
 var (
-	packageNameRegex = regexp.MustCompile(`\A[A-Za-z0-9\.\_\-\+]+\z`)
-	filenameRegex    = packageNameRegex
+	packageNameRegex = regexp.MustCompile(`\A[-_+.\w]+\z`)
+	filenameRegex    = regexp.MustCompile(`\A[-_+=:;.()\[\]{}~!@#$%^& \w]+\z`)
 )
 
 func apiError(ctx *context.Context, status int, obj any) {
@@ -54,20 +55,38 @@ func DownloadPackageFile(ctx *context.Context) {
 	helper.ServePackageFile(ctx, s, u, pf)
 }
 
+func isValidPackageName(packageName string) bool {
+	if len(packageName) == 1 && !unicode.IsLetter(rune(packageName[0])) && !unicode.IsNumber(rune(packageName[0])) {
+		return false
+	}
+	return packageNameRegex.MatchString(packageName) && packageName != ".."
+}
+
+func isValidFileName(filename string) bool {
+	return filenameRegex.MatchString(filename) &&
+		strings.TrimSpace(filename) == filename &&
+		filename != "." && filename != ".."
+}
+
 // UploadPackage uploads the specific generic package.
 // Duplicated packages get rejected.
 func UploadPackage(ctx *context.Context) {
 	packageName := ctx.Params("packagename")
 	filename := ctx.Params("filename")
 
-	if !packageNameRegex.MatchString(packageName) || !filenameRegex.MatchString(filename) {
-		apiError(ctx, http.StatusBadRequest, errors.New("Invalid package name or filename"))
+	if !isValidPackageName(packageName) {
+		apiError(ctx, http.StatusBadRequest, errors.New("invalid package name"))
+		return
+	}
+
+	if !isValidFileName(filename) {
+		apiError(ctx, http.StatusBadRequest, errors.New("invalid filename"))
 		return
 	}
 
 	packageVersion := ctx.Params("packageversion")
 	if packageVersion != strings.TrimSpace(packageVersion) {
-		apiError(ctx, http.StatusBadRequest, errors.New("Invalid package version"))
+		apiError(ctx, http.StatusBadRequest, errors.New("invalid package version"))
 		return
 	}
 
diff --git a/routers/api/packages/generic/generic_test.go b/routers/api/packages/generic/generic_test.go
new file mode 100644
index 0000000000..1acaafe576
--- /dev/null
+++ b/routers/api/packages/generic/generic_test.go
@@ -0,0 +1,65 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package generic
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestValidatePackageName(t *testing.T) {
+	bad := []string{
+		"",
+		".",
+		"..",
+		"-",
+		"a?b",
+		"a b",
+		"a/b",
+	}
+	for _, name := range bad {
+		assert.False(t, isValidPackageName(name), "bad=%q", name)
+	}
+
+	good := []string{
+		"a",
+		"1",
+		"a-",
+		"a_b",
+		"c.d+",
+	}
+	for _, name := range good {
+		assert.True(t, isValidPackageName(name), "good=%q", name)
+	}
+}
+
+func TestValidateFileName(t *testing.T) {
+	bad := []string{
+		"",
+		".",
+		"..",
+		"a?b",
+		"a/b",
+		" a",
+		"a ",
+	}
+	for _, name := range bad {
+		assert.False(t, isValidFileName(name), "bad=%q", name)
+	}
+
+	good := []string{
+		"-",
+		"a",
+		"1",
+		"a-",
+		"a_b",
+		"a b",
+		"c.d+",
+		`-_+=:;.()[]{}~!@#$%^& aA1`,
+	}
+	for _, name := range good {
+		assert.True(t, isValidFileName(name), "good=%q", name)
+	}
+}
diff --git a/tests/integration/api_packages_generic_test.go b/tests/integration/api_packages_generic_test.go
index 93525ac4b1..1cbae599af 100644
--- a/tests/integration/api_packages_generic_test.go
+++ b/tests/integration/api_packages_generic_test.go
@@ -84,7 +84,7 @@ func TestPackageGeneric(t *testing.T) {
 		t.Run("InvalidParameter", func(t *testing.T) {
 			defer tests.PrintCurrentTest(t)()
 
-			req := NewRequestWithBody(t, "PUT", fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, "invalid+package name", packageVersion, filename), bytes.NewReader(content)).
+			req := NewRequestWithBody(t, "PUT", fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, "invalid package name", packageVersion, filename), bytes.NewReader(content)).
 				AddBasicAuth(user.Name)
 			MakeRequest(t, req, http.StatusBadRequest)
 
@@ -92,7 +92,7 @@ func TestPackageGeneric(t *testing.T) {
 				AddBasicAuth(user.Name)
 			MakeRequest(t, req, http.StatusBadRequest)
 
-			req = NewRequestWithBody(t, "PUT", fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, packageName, packageVersion, "inval+id.na me"), bytes.NewReader(content)).
+			req = NewRequestWithBody(t, "PUT", fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, packageName, packageVersion, "inva|id.name"), bytes.NewReader(content)).
 				AddBasicAuth(user.Name)
 			MakeRequest(t, req, http.StatusBadRequest)
 		})