fix(feed): hide release notes in tags feed without releases access (#39944)

The tags feed (`tags.rss` / `tags.atom`) lives in the tags route group,
which only requires code read access, yet it lists every published
release next to the plain tags and includes each release's title and
rendered note. The releases page and `releases.rss` require releases
read access, and the tags page only shows tag names to users without it,
so the feed is the one place that leaks. A team member granted Code but
not Releases on a private repo, or anyone on a public repo whose
Releases unit is disabled, can read all release notes this way. I
noticed it while comparing the feed routes with the tags template. When
the doer cannot read releases, the feed now lists releases as plain
tags, linking to `/src/tag/` like the tags page does.
This commit is contained in:
Basavaraj S m authored and GitHub committed 2026-10-22 20:25:44 +00:00
1 parent 4bbe1b918a
commit 269b116b1b
2 files changed
+34 -13

No files matched your search

+16 -13
View File
@@ -15,6 +15,7 @@ import (
activities_model "gitea.dev/models/activities"
"gitea.dev/models/renderhelper"
repo_model "gitea.dev/models/repo"
"gitea.dev/models/unit"
"gitea.dev/modules/markup"
"gitea.dev/modules/markup/markdown"
"gitea.dev/modules/setting"
@@ -276,27 +277,29 @@ func GetFeedType(name string, req *http.Request) (showFeed bool, feedType string
// feedActionsToFeedItems convert gitea's Repo's Releases to feeds Item
func releasesToFeedItems(ctx *context.Context, releases []*repo_model.Release) (items []*feeds.Item, err error) {
canReadReleases := ctx.Repo.Permission.CanRead(unit.TypeReleases)
for _, rel := range releases {
err := rel.LoadAttributes(ctx)
if err != nil {
return nil, err
}
var title string
var content template.HTML
if rel.IsTag {
title = rel.TagName
} else {
title = rel.Title
}
title := rel.TagName
link := &feeds.Link{Href: rel.HTMLURL()}
rctx := renderhelper.NewRenderContextRepoComment(ctx, rel.Repo).WithUseAbsoluteLink(true)
content, err = markdown.RenderString(rctx,
rel.Note)
if err != nil {
return nil, err
if !canReadReleases {
// the tags feed only requires code access, so list releases as plain tags like the tags page does
link.Href = rel.Repo.HTMLURL() + "/src/tag/" + util.PathEscapeSegments(rel.TagName)
} else {
if !rel.IsTag {
title = rel.Title
}
rctx := renderhelper.NewRenderContextRepoComment(ctx, rel.Repo).WithUseAbsoluteLink(true)
content, err = markdown.RenderString(rctx, rel.Note)
if err != nil {
return nil, err
}
}
items = append(items, &feeds.Item{
+18
View File
@@ -9,9 +9,14 @@ import (
"testing"
auth_model "gitea.dev/models/auth"
repo_model "gitea.dev/models/repo"
unit_model "gitea.dev/models/unit"
"gitea.dev/models/unittest"
repo_service "gitea.dev/services/repository"
"gitea.dev/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFeedRepo(t *testing.T) {
@@ -33,6 +38,19 @@ func TestFeedRepo(t *testing.T) {
assert.Equal(t, "issue5", rss.Channel.Items[0].Description)
assert.NotEmpty(t, rss.Channel.Items[0].PubDate)
})
t.Run("TagsWithoutReleasesAccess", func(t *testing.T) {
defer tests.PrepareTestEnv(t)()
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
require.NoError(t, repo_service.UpdateRepositoryUnits(t.Context(), repo, nil, []unit_model.Type{unit_model.TypeReleases}))
resp := MakeRequest(t, NewRequest(t, "GET", "/user2/repo1/tags.rss"), http.StatusOK)
data := resp.Body.String()
assert.Contains(t, data, "/user2/repo1/src/tag/v1.0")
assert.NotContains(t, data, "pre-release")
assert.NotContains(t, data, "some text for a pre release")
})
}
// TestFeedRepoContentTokenScopes ensures repository feed endpoints enforce the