From caabd396ef6f40bb3401f7dbbcc4eeb8d75594ff Mon Sep 17 00:00:00 2001 From: Manak Raj <7manakraj@gmail.com> Date: Thu, 1 Jan 2026 19:31:28 +0530 Subject: [PATCH] fix(api): detect invalid UTF-8 in memo content before gRPC marshal Add defensive UTF-8 validation in convertMemoFromStore to prevent gRPC marshal failures when memo content or derived snippet contains invalid UTF-8 data. This fails fast with a clear INTERNAL error instead of crashing the Listen response. Signed-off-by: Manak Raj <7manakraj@gmail.com> --- server/router/api/v1/memo_service_converter.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/server/router/api/v1/memo_service_converter.go b/server/router/api/v1/memo_service_converter.go index 325c1a0b27677..2d9837933b18a 100644 --- a/server/router/api/v1/memo_service_converter.go +++ b/server/router/api/v1/memo_service_converter.go @@ -4,9 +4,12 @@ import ( "context" "fmt" "time" + "unicode/utf8" "github.com/pkg/errors" "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" v1pb "github.com/usememos/memos/proto/gen/api/v1" storepb "github.com/usememos/memos/proto/gen/store" @@ -25,6 +28,13 @@ func (s *APIV1Service) convertMemoFromStore(ctx context.Context, memo *store.Mem name := fmt.Sprintf("%s%s", MemoNamePrefix, memo.UID) memoMessage := &v1pb.Memo{ + if !utf8.ValidString(memo.Content) { + return nil, status.Errorf( + codes.Internal, + "memo %s contains invalid UTF-8 content", + memo.UID, + ) + } Name: name, State: convertStateFromStore(memo.RowStatus), Creator: fmt.Sprintf("%s%d", UserNamePrefix, memo.CreatorID), @@ -67,6 +77,13 @@ func (s *APIV1Service) convertMemoFromStore(ctx context.Context, memo *store.Mem } snippet, err := s.getMemoContentSnippet(memo.Content) + if !utf8.ValidString(snippet) { + return nil, status.Errorf( + codes.Internal, + "memo %s contains invalid UTF-8 snippet", + memo.UID, + ) + } if err != nil { return nil, errors.Wrap(err, "failed to get memo content snippet") }