VYPR
Low severityOSV Advisory· Published Jan 22, 2026· Updated Jan 23, 2026

Notification API Leaks Private Repository Issue Titles After Collaborator Permission Revocation

CVE-2026-20800

Description

Gitea's notification API does not re-validate repository access permissions when returning notification details. After a user's access to a private repository is revoked, they may still view issue and pull request titles through previously received notifications.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/go-gitea/giteaGo
< 1.25.41.25.4

Affected products

1

Patches

1
67e75f30a83d

Fix bug on notification read (#36339)

https://github.com/go-gitea/giteaLunny XiaoJan 16, 2026via ghsa
2 files changed · +69 6
  • services/convert/notification.go+12 6 modified
    @@ -8,8 +8,8 @@ import (
     	"net/url"
     
     	activities_model "code.gitea.io/gitea/models/activities"
    -	"code.gitea.io/gitea/models/perm"
     	access_model "code.gitea.io/gitea/models/perm/access"
    +	"code.gitea.io/gitea/modules/log"
     	api "code.gitea.io/gitea/modules/structs"
     )
     
    @@ -25,11 +25,17 @@ func ToNotificationThread(ctx context.Context, n *activities_model.Notification)
     
     	// since user only get notifications when he has access to use minimal access mode
     	if n.Repository != nil {
    -		result.Repository = ToRepo(ctx, n.Repository, access_model.Permission{AccessMode: perm.AccessModeRead})
    -
    -		// This permission is not correct and we should not be reporting it
    -		for repository := result.Repository; repository != nil; repository = repository.Parent {
    -			repository.Permissions = nil
    +		perm, err := access_model.GetUserRepoPermission(ctx, n.Repository, n.User)
    +		if err != nil {
    +			log.Error("GetUserRepoPermission failed: %v", err)
    +			return result
    +		}
    +		if perm.HasAnyUnitAccessOrPublicAccess() { // if user has been revoked access to repo, do not show repo info
    +			result.Repository = ToRepo(ctx, n.Repository, perm)
    +			// This permission is not correct and we should not be reporting it
    +			for repository := result.Repository; repository != nil; repository = repository.Parent {
    +				repository.Permissions = nil
    +			}
     		}
     	}
     
    
  • services/convert/notification_test.go+57 0 added
    @@ -0,0 +1,57 @@
    +// Copyright 2026 The Gitea Authors. All rights reserved.
    +// SPDX-License-Identifier: MIT
    +
    +package convert
    +
    +import (
    +	"testing"
    +
    +	activities_model "code.gitea.io/gitea/models/activities"
    +	repo_model "code.gitea.io/gitea/models/repo"
    +	"code.gitea.io/gitea/models/unittest"
    +	user_model "code.gitea.io/gitea/models/user"
    +	"code.gitea.io/gitea/modules/timeutil"
    +
    +	"github.com/stretchr/testify/assert"
    +)
    +
    +func TestToNotificationThreadIncludesRepoForAccessibleUser(t *testing.T) {
    +	assert.NoError(t, unittest.PrepareTestDatabase())
    +
    +	n := newRepoNotification(t, 1, 4)
    +	thread := ToNotificationThread(t.Context(), n)
    +
    +	if assert.NotNil(t, thread.Repository) {
    +		assert.Equal(t, n.Repository.FullName(), thread.Repository.FullName)
    +		assert.Nil(t, thread.Repository.Permissions)
    +	}
    +}
    +
    +func TestToNotificationThreadOmitsRepoWhenAccessRevoked(t *testing.T) {
    +	assert.NoError(t, unittest.PrepareTestDatabase())
    +
    +	n := newRepoNotification(t, 2, 4)
    +	thread := ToNotificationThread(t.Context(), n)
    +
    +	assert.Nil(t, thread.Repository)
    +}
    +
    +func newRepoNotification(t *testing.T, repoID, userID int64) *activities_model.Notification {
    +	t.Helper()
    +
    +	ctx := t.Context()
    +	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
    +	assert.NoError(t, repo.LoadOwner(ctx))
    +	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userID})
    +
    +	return &activities_model.Notification{
    +		ID:          repoID*1000 + userID,
    +		UserID:      user.ID,
    +		RepoID:      repo.ID,
    +		Status:      activities_model.NotificationStatusUnread,
    +		Source:      activities_model.NotificationSourceRepository,
    +		UpdatedUnix: timeutil.TimeStampNow(),
    +		Repository:  repo,
    +		User:        user,
    +	}
    +}
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.