From 1915b29a3c48e0a4a233067b57b7bd2f3372c11c Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 25 Apr 2026 12:34:55 -0700 Subject: [PATCH] fix(slack): stop block-based sender rehydration on assistant message edits (#71700) * fix(slack): stop block-based sender rehydration on message edits * docs(changelog): note Slack sender attribution fix --- CHANGELOG.md | 1 + .../slack/src/monitor/events/messages.test.ts | 29 +++++++++++++++++++ .../slack/src/monitor/events/messages.ts | 20 ------------- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ab8bb22a94..7fdf6bd79ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -187,6 +187,7 @@ Docs: https://docs.openclaw.ai - Sessions: make `sessions_spawn(mode="session")` errors name usable alternatives when the current channel cannot bind subagent threads. Fixes #67400. (#67790) Thanks @stainlu. - Agents/Claude CLI: pass the OpenClaw system prompt through Claude's prompt-file flag so Windows runs avoid argv length failures without changing system prompt semantics. Fixes #69158. (#69211) Thanks @skylee-01, @cassioanorte, @Syu0, and @Stache73. - Agents/CLI sessions: bind `google-gemini-cli` session auth-epoch to the Google account identity in `~/.gemini/oauth_creds.json`, so Gemini-backed agents resume their conversation after gateway restart instead of minting a fresh session, and stale bindings are invalidated when the authenticated Google account changes. Fixes #70973. (#71076) Thanks @openperf. +- Slack: stop treating user mentions in assistant-authored message edit blocks as sender attribution, preventing edited bot messages from spoofing a mentioned DM user. (#71700) Thanks @vincentkoc. ## 2026.4.24 diff --git a/extensions/slack/src/monitor/events/messages.test.ts b/extensions/slack/src/monitor/events/messages.test.ts index d44c2a97b56..8b60e0a1cc5 100644 --- a/extensions/slack/src/monitor/events/messages.test.ts +++ b/extensions/slack/src/monitor/events/messages.test.ts @@ -271,6 +271,35 @@ describe("registerSlackMessageEvents", () => { expect(messageQueueMock).not.toHaveBeenCalled(); }); + it("drops self-authored message_changed events that only include block user IDs", async () => { + const { handleSlackMessage } = await invokeRegisteredHandler({ + eventName: "message", + overrides: { dmPolicy: "open" }, + event: { + ...makeAssistantChangedEvent(), + message: { + ts: "123.456", + user: "U_BOT", + text: "preview edit with mention", + blocks: [ + { + type: "rich_text", + elements: [ + { + type: "rich_text_section", + elements: [{ type: "user", user_id: "UREAL123" }], + }, + ], + }, + ], + }, + }, + }); + + expect(handleSlackMessage).not.toHaveBeenCalled(); + expect(messageQueueMock).not.toHaveBeenCalled(); + }); + it("handles channel and group messages via the unified message handler", async () => { const { handler, handleSlackMessage } = createHandlers("message", { dmPolicy: "open", diff --git a/extensions/slack/src/monitor/events/messages.ts b/extensions/slack/src/monitor/events/messages.ts index 4a664844c06..91e792f8c3e 100644 --- a/extensions/slack/src/monitor/events/messages.ts +++ b/extensions/slack/src/monitor/events/messages.ts @@ -59,31 +59,12 @@ function collectMetadataUserCandidates( } } -function collectBlockUserIds(candidates: Set, value: unknown, botUserId: string): void { - if (Array.isArray(value)) { - for (const entry of value) { - collectBlockUserIds(candidates, entry, botUserId); - } - return; - } - const record = asRecord(value); - if (!record) { - return; - } - addUserCandidate(candidates, record.user_id, botUserId); - for (const key of ["elements", "accessory", "fields"]) { - collectBlockUserIds(candidates, record[key], botUserId); - } -} - function resolveAssistantMessageChangedSender(params: { - event: SlackMessageChangedEvent; message?: SlackAssistantMessageRecord; botUserId: string; }): string | undefined { const candidates = new Set(); collectMetadataUserCandidates(candidates, params.message?.metadata, params.botUserId); - collectBlockUserIds(candidates, params.message?.blocks, params.botUserId); return candidates.size === 1 ? [...candidates][0] : undefined; } @@ -122,7 +103,6 @@ function resolveAssistantMessageChangedInbound(params: { return undefined; } const senderId = resolveAssistantMessageChangedSender({ - event: changed, message, botUserId: params.ctx.botUserId, });