From cd0d45b184b7a7a610d31ce8807645a2e05e4375 Mon Sep 17 00:00:00 2001 From: Benedikt Schackenberg <6381261+BenediktSchackenberg@users.noreply.github.com> Date: Tue, 17 Mar 2026 19:44:25 +0000 Subject: [PATCH] fix(discord): fail-fast when bot identity fetch fails When fetchUser('@me') fails during Discord provider startup (e.g. due to transient network issues), the bot previously continued running with botUserId = undefined. This caused three security issues: 1. Mention gating bypassed: the guard 'if (botId && mentionGate.shouldSkip)' short-circuited when botId was undefined, letting all guild messages through even with requireMention: true. 2. Self-message filtering disabled: 'author.id === botUserId' never matched, risking self-reply loops. 3. Reply detection broken: replies to bot messages weren't recognized as implicit mentions. Fix: - Throw on fetchUser failure so auto-restart retries (matching the existing pattern for fetchDiscordApplicationId). - Also throw when fetchUser returns no id. - Defense-in-depth: remove the redundant 'botId &&' guard in the mention gate check so mentionGate.shouldSkip is evaluated regardless. Fixes #42219 --- .../src/monitor/message-handler.preflight.ts | 2 +- .../discord/src/monitor/provider.test.ts | 28 +++++++++++++++++++ extensions/discord/src/monitor/provider.ts | 9 ++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/extensions/discord/src/monitor/message-handler.preflight.ts b/extensions/discord/src/monitor/message-handler.preflight.ts index 9094cabb645..56b4569a118 100644 --- a/extensions/discord/src/monitor/message-handler.preflight.ts +++ b/extensions/discord/src/monitor/message-handler.preflight.ts @@ -788,7 +788,7 @@ export async function preflightDiscordMessage( `[discord-preflight] shouldRequireMention=${shouldRequireMention} baseRequireMention=${shouldRequireMentionByConfig} boundThreadSession=${isBoundThreadSession} mentionGate.shouldSkip=${mentionGate.shouldSkip} wasMentioned=${wasMentioned}`, ); if (isGuildMessage && shouldRequireMention) { - if (botId && mentionGate.shouldSkip) { + if (mentionGate.shouldSkip) { logDebug(`[discord-preflight] drop: no-mention`); logVerbose(`discord: drop guild message (mention required, botId=${botId})`); logger.info( diff --git a/extensions/discord/src/monitor/provider.test.ts b/extensions/discord/src/monitor/provider.test.ts index ff6fb310464..c43cbee1fd7 100644 --- a/extensions/discord/src/monitor/provider.test.ts +++ b/extensions/discord/src/monitor/provider.test.ts @@ -627,4 +627,32 @@ describe("monitorDiscordProvider", () => { const messages = vi.mocked(runtime.log).mock.calls.map((call) => String(call[0])); expect(messages.some((msg) => msg.includes("discord startup ["))).toBe(false); }); + + it("throws when fetchUser('@me') fails to prevent running without bot identity (#42219)", async () => { + const { monitorDiscordProvider } = await import("./provider.js"); + const runtime = baseRuntime(); + + clientFetchUserMock.mockRejectedValueOnce(new Error("network timeout")); + + await expect( + monitorDiscordProvider({ + config: baseConfig(), + runtime, + }), + ).rejects.toThrow("discord: cannot start without bot identity"); + }); + + it("throws when fetchUser('@me') returns no user id", async () => { + const { monitorDiscordProvider } = await import("./provider.js"); + const runtime = baseRuntime(); + + clientFetchUserMock.mockResolvedValueOnce({ id: undefined, username: "NoId" }); + + await expect( + monitorDiscordProvider({ + config: baseConfig(), + runtime, + }), + ).rejects.toThrow("discord: fetchUser('@me') returned no user id"); + }); }); diff --git a/extensions/discord/src/monitor/provider.ts b/extensions/discord/src/monitor/provider.ts index 8dbb6df29f5..aa38bf6cfc3 100644 --- a/extensions/discord/src/monitor/provider.ts +++ b/extensions/discord/src/monitor/provider.ts @@ -880,6 +880,15 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) { gateway: lifecycleGateway, details: String(err), }); + // Fail-fast: without botUserId, mention gating is bypassed (all guild + // messages pass through), self-message filtering is disabled (risk of + // self-reply loops), and reply detection is broken. Let auto-restart + // retry instead of running in a degraded state. See #42219. + throw new Error(`discord: cannot start without bot identity: ${String(err)}`); + } + if (!botUserId) { + // fetchUser succeeded but returned no id — equally unsafe to continue. + throw new Error("discord: fetchUser('@me') returned no user id"); } if (voiceEnabled) {