fix(release): validate web attachment renames against allowed types (#38314) (#38328)

Backport #38314 by @lunny

This fixes the web release edit flow so renamed release attachments are
validated against `[repository.release] ALLOWED_TYPES`.

Previously, the API attachment edit endpoint already enforced release
attachment type restrictions, but the web release edit form passed
`attachment-edit-*` values into `release_service.UpdateRelease`, which
updated attachment names directly without validating the new filename
against `setting.Repository.Release.AllowedTypes`.

As a result, a user with repository write access could rename an
existing release attachment to a disallowed extension through the web
UI.

- validate edited release attachment names in
`release_service.UpdateRelease`
- reject forbidden attachment renames using
  `setting.Repository.Release.AllowedTypes`
- re-render the web release edit page with a validation error instead of
  returning an internal server error
- add regression coverage for both the service layer and the web flow

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Giteabot
2026-07-04 08:45:11 -07:00
committed by GitHub
parent a9814e789c
commit 37c8604105
6 changed files with 93 additions and 78 deletions
+28 -4
View File
@@ -10,6 +10,7 @@ import (
repo_model "gitea.dev/models/repo"
"gitea.dev/models/unittest"
user_model "gitea.dev/models/user"
"gitea.dev/modules/setting"
"gitea.dev/modules/test"
"gitea.dev/modules/translation"
@@ -39,11 +40,10 @@ func createNewRelease(t *testing.T, session *TestSession, repoURL, tag, title st
if draft {
postData["draft"] = "1"
}
req = NewRequestWithValues(t, "POST", link, postData)
resp = session.MakeRequest(t, req, http.StatusSeeOther)
test.RedirectURL(resp) // check that redirect URL exists
resp = session.MakeRequest(t, req, http.StatusOK)
assert.NotEmpty(t, test.ParseJSONRedirect(resp.Body.Bytes()))
}
func checkLatestReleaseAndCount(t *testing.T, session *TestSession, repoURL, version, label string, count int) {
@@ -253,3 +253,27 @@ func TestDownloadReleaseAttachment(t *testing.T) {
session := loginUser(t, "user2")
session.MakeRequest(t, req, http.StatusOK)
}
func TestEditReleaseAttachmentRejectsForbiddenRename(t *testing.T) {
defer tests.PrepareTestEnv(t)()
defer test.MockVariableValue(&setting.Repository.Release.AllowedTypes, ".zip")()
attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 9})
release := unittest.AssertExistsAndLoadBean(t, &repo_model.Release{ID: attachment.ReleaseID})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: attachment.RepoID})
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
session := loginUser(t, repoOwner.Name)
req := NewRequestWithValues(t, "POST", fmt.Sprintf("%s/releases/edit/%s", repo.Link(), release.TagName), map[string]string{
"title": release.Title,
"content": release.Note,
"attachment-edit-" + attachment.UUID: "evil.exe",
})
resp := session.MakeRequest(t, req, http.StatusBadRequest)
errMsg := test.ParseJSONError(resp.Body.Bytes()).ErrorMessage
assert.Equal(t, "This file cannot be uploaded or modified due to a forbidden file extension or type.", errMsg)
attachment = unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: attachment.ID})
assert.NotEqual(t, "evil.exe", attachment.Name)
}