From 8228ceb89f23ff947124dfd68265733e8cfd80a4 Mon Sep 17 00:00:00 2001 From: Austin Date: Thu, 8 Oct 2026 19:43:19 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=A4=84=E7=90=86=E6=92=A4=E5=9B=9E?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E8=A7=A3=E5=AF=86=E6=97=B6=E7=9A=84500?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 撤回消息会清空content但保留key_version,新增判断跳过此类消息解密,避免整个查询窗口被毒化 --- server/store/message_crypto.go | 4 +++- server/store/message_crypto_test.go | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/server/store/message_crypto.go b/server/store/message_crypto.go index 6992974..6dadf49 100644 --- a/server/store/message_crypto.go +++ b/server/store/message_crypto.go @@ -196,7 +196,9 @@ func encryptMessageContent(msg *types.Message) (*types.Message, error) { } func decryptMessageContent(msg *types.Message) error { - if msg == nil || msg.KeyVersion <= 0 { + // 撤回消息的内容已被清空(recall 会把 head/content 置 NULL,但 content_nonce/key_version 残留), + // 此类行没有密文可解,直接放行让上层按已撤回形态下发,否则整个 get data 窗口会被单条撤回消息毒化成 500。 + if msg == nil || msg.KeyVersion <= 0 || msg.Content == nil { return nil } entry, err := getMessageKey(msg.TenantID, msg.KeyVersion) diff --git a/server/store/message_crypto_test.go b/server/store/message_crypto_test.go index 651e285..35cce28 100644 --- a/server/store/message_crypto_test.go +++ b/server/store/message_crypto_test.go @@ -82,6 +82,27 @@ func TestMessageCryptoRejectsTampering(t *testing.T) { } } +// 撤回消息 content 已被清 NULL,但 recall UPDATE 保留了 content_nonce/key_version; +// 读路径解密必须跳过此类行,否则单条撤回消息会把整个 get data 窗口毒化成 500(2026-10-08 测试环境实案)。 +func TestMessageCryptoSkipsRecalledContent(t *testing.T) { + tenantID := types.TenantID(9004) + key := bytes.Repeat([]byte{5}, 32) + expires := time.Now().Add(time.Minute) + messageKeyCache.Lock() + messageKeyCache.items[messageKeyCacheID(tenantID, 0)] = cachedMessageKey{enabled: true, version: 2, key: key, expires: expires} + messageKeyCache.items[messageKeyCacheID(tenantID, 2)] = cachedMessageKey{enabled: true, version: 2, key: key, expires: expires} + messageKeyCache.Unlock() + defer InvalidateMessageEncryptionKeys(tenantID) + + recalled := &types.Message{TenantID: tenantID, Topic: "p2p-recalled", SeqId: 64, KeyVersion: 2, ContentNonce: bytes.Repeat([]byte{1}, 12), Content: nil} + if err := decryptMessageContent(recalled); err != nil { + t.Fatalf("expected recalled message with nil content to be skipped, got: %v", err) + } + if recalled.Content != nil { + t.Fatalf("nil content should stay nil: %#v", recalled.Content) + } +} + func TestMessageCryptoSearchMatchesDecryptedContent(t *testing.T) { content := map[string]any{"txt": "项目 验收通过", "nested": map[string]any{"title": "日报"}} if !messageContentMatchesSearch(content, "验收") {