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, });