From ee52f64226a03efadfdf1e3b759e13424a3d4e41 Mon Sep 17 00:00:00 2001 From: Jacob Tomlinson Date: Mon, 30 Mar 2026 06:38:22 -0700 Subject: [PATCH] Discord: gate audio preflight on member access (#57695) * Discord: gate audio preflight on member access * Discord: trim unauthorized sender logging * CI: retrigger after review follow-up * Discord: document blocked-sender log privacy --- .../monitor/message-handler.preflight.test.ts | 60 +++++++++++++++++++ .../src/monitor/message-handler.preflight.ts | 30 +++++----- 2 files changed, 75 insertions(+), 15 deletions(-) diff --git a/extensions/discord/src/monitor/message-handler.preflight.test.ts b/extensions/discord/src/monitor/message-handler.preflight.test.ts index 19bca048a1b..46c9b0ebc4d 100644 --- a/extensions/discord/src/monitor/message-handler.preflight.test.ts +++ b/extensions/discord/src/monitor/message-handler.preflight.test.ts @@ -765,6 +765,66 @@ describe("preflightDiscordMessage", () => { expect(result?.wasMentioned).toBe(true); }); + it("does not transcribe guild audio from unauthorized members", async () => { + const channelId = "channel-audio-unauthorized-1"; + const guildId = "guild-audio-unauthorized-1"; + const client = createGuildTextClient(channelId); + + const message = createDiscordMessage({ + id: "m-audio-unauthorized-1", + channelId, + content: "", + attachments: [ + { + id: "att-1", + url: "https://cdn.discordapp.com/attachments/voice.ogg", + content_type: "audio/ogg", + filename: "voice.ogg", + }, + ], + author: { + id: "user-2", + bot: false, + username: "Mallory", + }, + }); + + const result = await preflightDiscordMessage({ + ...createPreflightArgs({ + cfg: { + ...DEFAULT_PREFLIGHT_CFG, + messages: { + groupChat: { + mentionPatterns: ["openclaw"], + }, + }, + } as import("openclaw/plugin-sdk/config-runtime").OpenClawConfig, + discordConfig: {} as DiscordConfig, + data: createGuildEvent({ + channelId, + guildId, + author: message.author, + message, + }), + client, + }), + guildEntries: { + [guildId]: { + channels: { + [channelId]: { + allow: true, + requireMention: true, + users: ["user-1"], + }, + }, + }, + }, + }); + + expect(transcribeFirstAudioMock).not.toHaveBeenCalled(); + expect(result).toBeNull(); + }); + it("drops guild message without mention when channel has configuredBinding and requireMention: true", async () => { const conversationRuntime = await import("openclaw/plugin-sdk/conversation-runtime"); const channelId = "ch-binding-1"; diff --git a/extensions/discord/src/monitor/message-handler.preflight.ts b/extensions/discord/src/monitor/message-handler.preflight.ts index 4813e33a818..39bfbde3554 100644 --- a/extensions/discord/src/monitor/message-handler.preflight.ts +++ b/extensions/discord/src/monitor/message-handler.preflight.ts @@ -679,9 +679,22 @@ export async function preflightDiscordMessage( shouldRequireMention: shouldRequireMentionByConfig, bypassMentionRequirement, }); + const { hasAccessRestrictions, memberAllowed } = resolveDiscordMemberAccessState({ + channelConfig, + guildInfo, + memberRoleIds, + sender, + allowNameMatching, + }); - // Preflight audio transcription for mention detection in guilds. - // This allows voice notes to be checked for mentions before being dropped. + if (isGuildMessage && hasAccessRestrictions && !memberAllowed) { + logDebug(`[discord-preflight] drop: member not allowed`); + // Keep stable Discord user IDs out of routine deny-path logs. + logVerbose("Blocked discord guild sender (not in users/roles allowlist)"); + return null; + } + + // Only authorized guild senders should reach the expensive transcription path. const { hasTypedText, transcript: preflightTranscript } = await resolveDiscordPreflightAudioMentionContext({ message, @@ -725,13 +738,6 @@ export async function preflightDiscordMessage( surface: "discord", }); const hasControlCommandInMessage = hasControlCommand(baseText, params.cfg); - const { hasAccessRestrictions, memberAllowed } = resolveDiscordMemberAccessState({ - channelConfig, - guildInfo, - memberRoleIds, - sender, - allowNameMatching, - }); if (!isDirectMessage) { const { ownerAllowList, ownerAllowed: ownerOk } = resolveDiscordOwnerAccess({ @@ -834,12 +840,6 @@ export async function preflightDiscordMessage( return null; } - if (isGuildMessage && hasAccessRestrictions && !memberAllowed) { - logDebug(`[discord-preflight] drop: member not allowed`); - logVerbose(`Blocked discord guild sender ${sender.id} (not in users/roles allowlist)`); - return null; - } - const systemLocation = resolveDiscordSystemLocation({ isDirectMessage, isGroupDm,