fix(packages): validate module version in goproxy ParsePackage (#38104)

**Unvalidated version in goproxy ParsePackage**
The module version is read straight from the zip directory path and
never checked, so a crafted upload can leave a newline in it;
`EnumeratePackageVersions` then writes each stored version on its own
line for the `@v/list` endpoint, letting a module advertise fabricated
versions to `go` clients. Validated the parsed version with
`semver.IsValid` inside the parser, matching the version checks the
other package parsers already do.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
metsw24-max
2026-06-15 22:44:14 +05:30
committed by GitHub
parent 052feee34a
commit 0eba0e371f
3 changed files with 21 additions and 1 deletions

View File

@@ -10,6 +10,8 @@ import (
"strings"
"gitea.dev/modules/util"
"golang.org/x/mod/semver"
)
const (
@@ -20,6 +22,7 @@ const (
var (
ErrInvalidStructure = util.NewInvalidArgumentErrorf("package has invalid structure")
ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
ErrGoModFileTooLarge = util.NewInvalidArgumentErrorf("go.mod file is too large")
)
@@ -54,6 +57,13 @@ func ParsePackage(r io.ReaderAt, size int64) (*Package, error) {
Name: strings.TrimSuffix(nameAndVersion, "@"+parts[1]),
Version: versionParts[0],
}
// the version is taken verbatim from the zip path and later written
// one per line into the @v/list proxy response, so it has to be a
// valid module version (no newlines or other stray characters)
if !semver.IsValid(p.Version) {
return nil, ErrInvalidVersion
}
}
if len(versionParts) > 1 {

View File

@@ -59,6 +59,16 @@ func TestParsePackage(t *testing.T) {
assert.Equal(t, "module gitea.com/go-gitea/gitea", p.GoMod)
})
t.Run("InvalidVersion", func(t *testing.T) {
data := createArchive(map[string][]byte{
packageName + "@v1.0.0\nv99.0.0/go.mod": []byte("module " + packageName),
})
p, err := ParsePackage(data, int64(data.Len()))
assert.Nil(t, p)
assert.ErrorIs(t, err, ErrInvalidVersion)
})
t.Run("Valid", func(t *testing.T) {
data := createArchive(map[string][]byte{
packageName + "@" + packageVersion + "/subdir/go.mod": []byte("invalid"),