From a724dadad138275c517344e4f88ee99cae42efe0 Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Tue, 8 Sep 2026 18:37:39 +0700 Subject: [PATCH] DM audit: stop group messages leaking into 1:1 threads/list (1.2.42) NIP-17 group messages (rumor with >1 recipient p-tag) were bleeding into the 1:1 surfaces: - useConversation (thread view): a group message from `otherPubkey` where I was also a recipient satisfied isFromOther && isToMe, so group chatter appeared inside the 1:1 thread. - useConversations (list): group messages were collapsed to conversationWith = sender (or an arbitrary recipient for my own sends), spawning phantom 1:1 entries and double-listing every group (once here, once in useGroupConversations). Both 1:1 hooks now skip any rumor with more than one p-tag. Groups remain the sole responsibility of the useGroupConversation(s) hooks, which already require pTags.length > 1. Genuine 1:1 rumors carry exactly one recipient p-tag (createRumor doesn't tag the sender), so no legitimate message is dropped. Verified solid (no changes): the seal(13)/rumor(14) kind checks, the rumor.pubkey === seal.pubkey impersonation guard, and the amberSafeQueryOptions decrypt-storm protection for interactive signers are all correct. Co-authored-by: shakespeare.diy --- package.json | 2 +- src/hooks/useDirectMessages.ts | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 700e001..dcba20c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "skate-spots", "private": true, - "version": "1.2.41", + "version": "1.2.42", "type": "module", "homepage": "https://skate-spots.com", "scripts": { diff --git a/src/hooks/useDirectMessages.ts b/src/hooks/useDirectMessages.ts index f63f235..ca2f170 100644 --- a/src/hooks/useDirectMessages.ts +++ b/src/hooks/useDirectMessages.ts @@ -322,6 +322,12 @@ export function useConversation(otherPubkey: string) { // Check if this message is part of the conversation with otherPubkey const pTags = rumor.tags.filter(t => t[0] === 'p').map(t => t[1]); + + // 1:1 only. A rumor with more than one recipient is a GROUP message + // (handled by useGroupConversation). Without this guard a group chat + // leaks into the 1:1 thread with each of its participants. + if (pTags.length > 1) continue; + const isFromOther = rumor.pubkey === otherPubkey; const isToOther = pTags.includes(otherPubkey); const isFromMe = rumor.pubkey === user.pubkey; @@ -428,6 +434,13 @@ export function useConversations() { // Determine conversation partner const pTags = rumor.tags.filter(t => t[0] === 'p').map(t => t[1]); + + // 1:1 list only. A rumor addressed to more than one recipient is a + // GROUP message (surfaced by useGroupConversations). Without this + // guard each group would masquerade as a phantom 1:1 with whichever + // participant happened to speak, and would appear twice in the UI. + if (pTags.length > 1) continue; + let conversationWith: string; if (rumor.pubkey === user.pubkey) {