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

Backport #39782 by @basavaraj-sm05

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: Basavaraj S m <basavaraj@digiscrypt.com>
Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
authored and GitHub committed 2026-10-21 05:24:52 +00:00
1 parent 31653bdb34
commit 4b743d2e8c
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/util"
@@ -23,6 +25,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 {
@@ -220,6 +224,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) {