fix(packages): reject newline-bearing rubygems dependency metadata (#39782)

The RubyGems compact index (the shared `/versions` file and per-package
`info/<name>` files) is line-based.
https://github.com/go-gitea/gitea/pull/38061 rejected line breaks in the
gem name, but the platform, dependency names and version requirements
from the gemspec still accepted them, letting an uploader forge index
lines for bundler clients.

The parser now rejects a line break in any of those fields. The Marshal
based endpoints are length prefixed and unaffected.

Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
Basavaraj S mandsilverwind authored and GitHub committed 2026-10-20 21:18:44 +00:00
1 parent 738ff1d65a
commit c52e7c0cd2
2 files changed
+30

No files matched your search

+17
View File
@@ -8,6 +8,8 @@ import (
"compress/gzip"
"io"
"regexp"
"slices"
"strings"
"sync"
"gitea.dev/modules/packages"
@@ -24,6 +26,8 @@ var (
ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid")
// ErrInvalidVersion indicates an invalid version in the metadata.gz file
ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
// ErrInvalidMetadata indicates metadata that would corrupt the generated index
ErrInvalidMetadata = util.NewInvalidArgumentErrorf("package metadata is invalid")
)
var globalVars = sync.OnceValue(func() (ret struct {
@@ -221,6 +225,19 @@ func parseMetadataFile(r io.Reader) (*Package, error) {
}
}
values := []string{m.Platform}
reqs := slices.Concat(m.RequiredRubyVersion, m.RequiredRubygemsVersion)
for _, dep := range slices.Concat(m.RuntimeDependencies, m.DevelopmentDependencies) {
values = append(values, dep.Name)
reqs = append(reqs, dep.Version...)
}
for _, req := range reqs {
values = append(values, req.Restriction, req.Version)
}
if slices.ContainsFunc(values, func(s string) bool { return strings.ContainsAny(s, "\r\n") }) {
return nil, ErrInvalidMetadata // the compact index is line-based, a line break would forge entries
}
return &Package{
Name: spec.Name,
Version: spec.Version.Version,
@@ -43,6 +43,19 @@ version:
assert.Nil(t, rp)
}
})
t.Run("LineBreakInIndexedMetadata", func(t *testing.T) {
for _, field := range []string{
`platform: "ruby\nx"`,
`dependencies: [{name: "d\nx"}]`,
`dependencies: [{name: d, requirement: {requirements: [[">=", {version: "1\nx"}]]}}]`,
`required_ruby_version: {requirements: [[">=", {version: "1\nx"}]]}`,
} {
rp, err := parseMetadataFile(test.CompressGzip("name: g\nversion:\n version: 1\n" + field + "\n"))
assert.ErrorIs(t, err, ErrInvalidMetadata, field)
assert.Nil(t, rp)
}
})
}
func TestParseMetadataFile(t *testing.T) {