VYPR
Moderate severityOSV Advisory· Published Dec 8, 2025· Updated Dec 8, 2025

CVE-2025-65798

CVE-2025-65798

Description

Incorrect access control in usememos memos v0.25.2 allows attackers with low-level privileges to arbitrarily modify or delete attachments made by other users.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/usememos/memosGo
< 0.25.30.25.3

Affected products

1

Patches

1
769dcd0cf9be

fix(security): add missing authorization checks to various services (#5217)

https://github.com/usememos/memosFlorian DewaldNov 6, 2025via ghsa
6 files changed · +138 6
  • server/router/api/v1/idp_service.go+46 2 modified
    @@ -38,8 +38,17 @@ func (s *APIV1Service) ListIdentityProviders(ctx context.Context, _ *v1pb.ListId
     	response := &v1pb.ListIdentityProvidersResponse{
     		IdentityProviders: []*v1pb.IdentityProvider{},
     	}
    +
    +	// Default to lowest-privilege role, update later based on real role
    +	currentUserRole := store.RoleUser
    +	currentUser, err := s.GetCurrentUser(ctx)
    +	if err == nil && currentUser != nil {
    +		currentUserRole = currentUser.Role
    +	}
    +
     	for _, identityProvider := range identityProviders {
    -		response.IdentityProviders = append(response.IdentityProviders, convertIdentityProviderFromStore(identityProvider))
    +		identityProviderConverted := convertIdentityProviderFromStore(identityProvider)
    +		response.IdentityProviders = append(response.IdentityProviders, redactIdentityProviderResponse(identityProviderConverted, currentUserRole))
     	}
     	return response, nil
     }
    @@ -58,10 +67,27 @@ func (s *APIV1Service) GetIdentityProvider(ctx context.Context, request *v1pb.Ge
     	if identityProvider == nil {
     		return nil, status.Errorf(codes.NotFound, "identity provider not found")
     	}
    -	return convertIdentityProviderFromStore(identityProvider), nil
    +
    +	// Default to lowest-privilege role, update later based on real role
    +	currentUserRole := store.RoleUser
    +	currentUser, err := s.GetCurrentUser(ctx)
    +	if err == nil && currentUser != nil {
    +		currentUserRole = currentUser.Role
    +	}
    +
    +	identityProviderConverted := convertIdentityProviderFromStore(identityProvider)
    +	return redactIdentityProviderResponse(identityProviderConverted, currentUserRole), nil
     }
     
     func (s *APIV1Service) UpdateIdentityProvider(ctx context.Context, request *v1pb.UpdateIdentityProviderRequest) (*v1pb.IdentityProvider, error) {
    +	currentUser, err := s.GetCurrentUser(ctx)
    +	if err != nil {
    +		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
    +	}
    +	if currentUser == nil || currentUser.Role != store.RoleHost {
    +		return nil, status.Errorf(codes.PermissionDenied, "permission denied")
    +	}
    +
     	if request.UpdateMask == nil || len(request.UpdateMask.Paths) == 0 {
     		return nil, status.Errorf(codes.InvalidArgument, "update_mask is required")
     	}
    @@ -95,6 +121,14 @@ func (s *APIV1Service) UpdateIdentityProvider(ctx context.Context, request *v1pb
     }
     
     func (s *APIV1Service) DeleteIdentityProvider(ctx context.Context, request *v1pb.DeleteIdentityProviderRequest) (*emptypb.Empty, error) {
    +	currentUser, err := s.GetCurrentUser(ctx)
    +	if err != nil {
    +		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
    +	}
    +	if currentUser == nil || currentUser.Role != store.RoleHost {
    +		return nil, status.Errorf(codes.PermissionDenied, "permission denied")
    +	}
    +
     	id, err := ExtractIdentityProviderIDFromName(request.Name)
     	if err != nil {
     		return nil, status.Errorf(codes.InvalidArgument, "invalid identity provider name: %v", err)
    @@ -183,3 +217,13 @@ func convertIdentityProviderConfigToStore(identityProviderType v1pb.IdentityProv
     	}
     	return nil
     }
    +
    +func redactIdentityProviderResponse(identityProvider *v1pb.IdentityProvider, userRole store.Role) *v1pb.IdentityProvider {
    +	if userRole != store.RoleHost {
    +		if identityProvider.Type == v1pb.IdentityProvider_OAUTH2 {
    +			identityProvider.Config.GetOauth2Config().ClientSecret = ""
    +		}
    +	}
    +
    +	return identityProvider
    +}
    
  • server/router/api/v1/memo_attachment_service.go+10 0 modified
    @@ -14,6 +14,13 @@ import (
     )
     
     func (s *APIV1Service) SetMemoAttachments(ctx context.Context, request *v1pb.SetMemoAttachmentsRequest) (*emptypb.Empty, error) {
    +	user, err := s.GetCurrentUser(ctx)
    +	if err != nil {
    +		return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
    +	}
    +	if user == nil {
    +		return nil, status.Errorf(codes.Unauthenticated, "user not authenticated")
    +	}
     	memoUID, err := ExtractMemoUIDFromName(request.Name)
     	if err != nil {
     		return nil, status.Errorf(codes.InvalidArgument, "invalid memo name: %v", err)
    @@ -22,6 +29,9 @@ func (s *APIV1Service) SetMemoAttachments(ctx context.Context, request *v1pb.Set
     	if err != nil {
     		return nil, status.Errorf(codes.Internal, "failed to get memo")
     	}
    +	if memo.CreatorID != user.ID && !isSuperUser(user) {
    +		return nil, status.Errorf(codes.PermissionDenied, "permission denied")
    +	}
     	attachments, err := s.Store.ListAttachments(ctx, &store.FindAttachment{
     		MemoID: &memo.ID,
     	})
    
  • server/router/api/v1/memo_relation_service.go+10 0 modified
    @@ -14,6 +14,13 @@ import (
     )
     
     func (s *APIV1Service) SetMemoRelations(ctx context.Context, request *v1pb.SetMemoRelationsRequest) (*emptypb.Empty, error) {
    +	user, err := s.GetCurrentUser(ctx)
    +	if err != nil {
    +		return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
    +	}
    +	if user == nil {
    +		return nil, status.Errorf(codes.Unauthenticated, "user not authenticated")
    +	}
     	memoUID, err := ExtractMemoUIDFromName(request.Name)
     	if err != nil {
     		return nil, status.Errorf(codes.InvalidArgument, "invalid memo name: %v", err)
    @@ -22,6 +29,9 @@ func (s *APIV1Service) SetMemoRelations(ctx context.Context, request *v1pb.SetMe
     	if err != nil {
     		return nil, status.Errorf(codes.Internal, "failed to get memo")
     	}
    +	if memo.CreatorID != user.ID && !isSuperUser(user) {
    +		return nil, status.Errorf(codes.PermissionDenied, "permission denied")
    +	}
     	referenceType := store.MemoRelationReference
     	// Delete all reference relations first.
     	if err := s.Store.DeleteMemoRelation(ctx, &store.DeleteMemoRelation{
    
  • server/router/api/v1/reaction_service.go+24 0 modified
    @@ -55,11 +55,35 @@ func (s *APIV1Service) UpsertMemoReaction(ctx context.Context, request *v1pb.Ups
     }
     
     func (s *APIV1Service) DeleteMemoReaction(ctx context.Context, request *v1pb.DeleteMemoReactionRequest) (*emptypb.Empty, error) {
    +	user, err := s.GetCurrentUser(ctx)
    +	if err != nil {
    +		return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
    +	}
    +	if user == nil {
    +		return nil, status.Errorf(codes.Unauthenticated, "user not authenticated")
    +	}
    +
     	reactionID, err := ExtractReactionIDFromName(request.Name)
     	if err != nil {
     		return nil, status.Errorf(codes.InvalidArgument, "invalid reaction name: %v", err)
     	}
     
    +	// Get reaction and check ownership
    +	reactions, err := s.Store.ListReactions(ctx, &store.FindReaction{
    +		ID: &reactionID,
    +	})
    +	if err != nil {
    +		return nil, status.Errorf(codes.Internal, "failed to list reactions")
    +	}
    +	if len(reactions) == 0 {
    +		return nil, status.Errorf(codes.NotFound, "reaction not found")
    +	}
    +
    +	reaction := reactions[0]
    +	if reaction.CreatorID != user.ID && !isSuperUser(user) {
    +		return nil, status.Errorf(codes.PermissionDenied, "permission denied")
    +	}
    +
     	if err := s.Store.DeleteReaction(ctx, &store.DeleteReaction{
     		ID: reactionID,
     	}); err != nil {
    
  • server/router/api/v1/test/idp_service_test.go+37 4 modified
    @@ -233,6 +233,7 @@ func TestGetIdentityProvider(t *testing.T) {
     			Name: created.Name,
     		}
     
    +		// Test unauthenticated, should not contain client secret
     		resp, err := ts.Service.GetIdentityProvider(ctx, getReq)
     		require.NoError(t, err)
     		require.NotNil(t, resp)
    @@ -241,7 +242,18 @@ func TestGetIdentityProvider(t *testing.T) {
     		require.Equal(t, v1pb.IdentityProvider_OAUTH2, resp.Type)
     		require.NotNil(t, resp.Config.GetOauth2Config())
     		require.Equal(t, "test-client", resp.Config.GetOauth2Config().ClientId)
    -		require.Equal(t, "test-secret", resp.Config.GetOauth2Config().ClientSecret)
    +		require.Equal(t, "", resp.Config.GetOauth2Config().ClientSecret)
    +
    +		// Test as host user, should contain client secret
    +		respHostUser, err := ts.Service.GetIdentityProvider(userCtx, getReq)
    +		require.NoError(t, err)
    +		require.NotNil(t, respHostUser)
    +		require.Equal(t, created.Name, respHostUser.Name)
    +		require.Equal(t, "Test Provider", respHostUser.Title)
    +		require.Equal(t, v1pb.IdentityProvider_OAUTH2, respHostUser.Type)
    +		require.NotNil(t, respHostUser.Config.GetOauth2Config())
    +		require.Equal(t, "test-client", respHostUser.Config.GetOauth2Config().ClientId)
    +		require.Equal(t, "test-secret", respHostUser.Config.GetOauth2Config().ClientSecret)
     	})
     
     	t.Run("GetIdentityProvider not found", func(t *testing.T) {
    @@ -353,14 +365,21 @@ func TestUpdateIdentityProvider(t *testing.T) {
     		ts := NewTestService(t)
     		defer ts.Cleanup()
     
    +		// Create host user
    +		hostUser, err := ts.CreateHostUser(ctx, "admin")
    +		require.NoError(t, err)
    +
    +		// Set user context
    +		userCtx := ts.CreateUserContext(ctx, hostUser.ID)
    +
     		req := &v1pb.UpdateIdentityProviderRequest{
     			IdentityProvider: &v1pb.IdentityProvider{
     				Name:  "identity-providers/1",
     				Title: "Updated Provider",
     			},
     		}
     
    -		_, err := ts.Service.UpdateIdentityProvider(ctx, req)
    +		_, err = ts.Service.UpdateIdentityProvider(userCtx, req)
     		require.Error(t, err)
     		require.Contains(t, err.Error(), "update_mask is required")
     	})
    @@ -369,6 +388,13 @@ func TestUpdateIdentityProvider(t *testing.T) {
     		ts := NewTestService(t)
     		defer ts.Cleanup()
     
    +		// Create host user
    +		hostUser, err := ts.CreateHostUser(ctx, "admin")
    +		require.NoError(t, err)
    +
    +		// Set user context
    +		userCtx := ts.CreateUserContext(ctx, hostUser.ID)
    +
     		req := &v1pb.UpdateIdentityProviderRequest{
     			IdentityProvider: &v1pb.IdentityProvider{
     				Name:  "invalid-name",
    @@ -379,7 +405,7 @@ func TestUpdateIdentityProvider(t *testing.T) {
     			},
     		}
     
    -		_, err := ts.Service.UpdateIdentityProvider(ctx, req)
    +		_, err = ts.Service.UpdateIdentityProvider(userCtx, req)
     		require.Error(t, err)
     		require.Contains(t, err.Error(), "invalid identity provider name")
     	})
    @@ -445,11 +471,18 @@ func TestDeleteIdentityProvider(t *testing.T) {
     		ts := NewTestService(t)
     		defer ts.Cleanup()
     
    +		// Create host user
    +		hostUser, err := ts.CreateHostUser(ctx, "admin")
    +		require.NoError(t, err)
    +
    +		// Set user context
    +		userCtx := ts.CreateUserContext(ctx, hostUser.ID)
    +
     		req := &v1pb.DeleteIdentityProviderRequest{
     			Name: "invalid-name",
     		}
     
    -		_, err := ts.Service.DeleteIdentityProvider(ctx, req)
    +		_, err = ts.Service.DeleteIdentityProvider(userCtx, req)
     		require.Error(t, err)
     		require.Contains(t, err.Error(), "invalid identity provider name")
     	})
    
  • server/router/api/v1/user_service.go+11 0 modified
    @@ -169,6 +169,17 @@ func (s *APIV1Service) CreateUser(ctx context.Context, request *v1pb.CreateUserR
     			// Unauthenticated or non-HOST users can only create normal users
     			roleToAssign = store.RoleUser
     		}
    +
    +		// Only allow user registration if it is enabled in the settings, or if the user is a superuser
    +		if currentUser == nil || !isSuperUser(currentUser) {
    +			workspaceGeneralSetting, err := s.Store.GetWorkspaceGeneralSetting(ctx)
    +			if err != nil {
    +				return nil, status.Errorf(codes.Internal, "failed to get workspace general setting, error: %v", err)
    +			}
    +			if workspaceGeneralSetting.DisallowUserRegistration {
    +				return nil, status.Errorf(codes.PermissionDenied, "user registration is not allowed")
    +			}
    +		}
     	}
     
     	if !base.UIDMatcher.MatchString(strings.ToLower(request.User.Username)) {
    

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.