From 920b5f1e6835de9332e1ebf3747807c9b9e7580f Mon Sep 17 00:00:00 2001 From: bircni Date: Fri, 21 Aug 2026 09:06:56 +0200 Subject: [PATCH] fix(api): enforce public-only scope for compare heads (#39006) Enforce public-only token scope for repositories resolved as compare heads. _Assisted-by: Codex:GPT-5_ --- routers/api/v1/repo/pull.go | 4 ++++ tests/integration/api_repo_compare_test.go | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index a77226e3bf1..cdc0ec00c89 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -1094,6 +1094,10 @@ func parseCompareInfo(ctx *context.APIContext, compareParam string) (result *git ctx.APIErrorInternal(err) return nil, nil } + if !ctx.TokenCanAccessRepo(headRepo) { + ctx.APIErrorNotFound() + return nil, nil + } isSameRepo := baseRepo.ID == headRepo.ID diff --git a/tests/integration/api_repo_compare_test.go b/tests/integration/api_repo_compare_test.go index d750e720625..57f943c5adf 100644 --- a/tests/integration/api_repo_compare_test.go +++ b/tests/integration/api_repo_compare_test.go @@ -98,6 +98,26 @@ func TestAPICompareBranches(t *testing.T) { }) } +func TestAPIComparePublicOnlyToken(t *testing.T) { + onGiteaRun(t, func(t *testing.T, _ *url.URL) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + org26 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 26}) + pr := createOutdatedPR(t, user, org26) + require.NoError(t, pr.LoadBaseRepo(t.Context())) + require.NoError(t, pr.LoadHeadRepo(t.Context())) + + require.NoError(t, repo_model.UpdateRepositoryColsNoAutoTime(t.Context(), + &repo_model.Repository{ID: pr.HeadRepo.ID, IsPrivate: true}, "is_private")) + + compareURL := "/api/v1/repos/" + pr.BaseRepo.FullName() + "/compare/" + pr.BaseBranch + "..." + pr.HeadRepo.OwnerName + ":" + pr.HeadBranch + fullToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadRepository) + MakeRequest(t, NewRequest(t, "GET", compareURL).AddTokenAuth(fullToken), http.StatusOK) + + publicOnlyToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly) + MakeRequest(t, NewRequest(t, "GET", compareURL).AddTokenAuth(publicOnlyToken), http.StatusNotFound) + }) +} + func TestAPIDownloadCompareDiffOrPatch(t *testing.T) { onGiteaRun(t, func(t *testing.T, _ *url.URL) { session := loginUser(t, "user2")