diff --git a/extensions/discord/src/monitor/native-command.commands-allowfrom.test.ts b/extensions/discord/src/monitor/native-command.commands-allowfrom.test.ts index 92efa3eaecd..87a65d4cf95 100644 --- a/extensions/discord/src/monitor/native-command.commands-allowfrom.test.ts +++ b/extensions/discord/src/monitor/native-command.commands-allowfrom.test.ts @@ -167,6 +167,36 @@ describe("Discord native slash commands with commands.allowFrom", () => { expectUnauthorizedReply(interaction); }); + it("authorizes guild slash commands when commands.allowFrom.discord contains a matching guild: entry", async () => { + const { dispatchSpy, interaction } = await runGuildSlashCommand({ + userId: "999999999999999999", + mutateConfig: (cfg) => { + cfg.commands = { + allowFrom: { + discord: ["guild:345678901234567890"], + }, + }; + }, + }); + expect(dispatchSpy).toHaveBeenCalledTimes(1); + expectNotUnauthorizedReply(interaction); + }); + + it("rejects guild slash commands when commands.allowFrom.discord has a guild: entry that does not match", async () => { + const { dispatchSpy, interaction } = await runGuildSlashCommand({ + userId: "999999999999999999", + mutateConfig: (cfg) => { + cfg.commands = { + allowFrom: { + discord: ["guild:000000000000000000"], + }, + }; + }, + }); + expect(dispatchSpy).not.toHaveBeenCalled(); + expectUnauthorizedReply(interaction); + }); + it("uses the root discord maxLinesPerMessage when runtime discordConfig omits it", async () => { const longReply = Array.from({ length: 20 }, (_value, index) => `Line ${index + 1}`).join("\n"); const { interaction } = await runGuildSlashCommand({ diff --git a/extensions/discord/src/monitor/native-command.ts b/extensions/discord/src/monitor/native-command.ts index a9f17a697a1..e8affbcd376 100644 --- a/extensions/discord/src/monitor/native-command.ts +++ b/extensions/discord/src/monitor/native-command.ts @@ -144,6 +144,7 @@ function resolveDiscordNativeCommandAllowlistAccess(params: { sender: { id: string; name?: string; tag?: string }; chatType: "direct" | "group" | "thread" | "channel"; conversationId?: string; + guildId?: string | null; }) { const commandsAllowFrom = params.cfg.commands?.allowFrom; if (!commandsAllowFrom || typeof commandsAllowFrom !== "object") { @@ -155,6 +156,16 @@ function resolveDiscordNativeCommandAllowlistAccess(params: { if (!Array.isArray(rawAllowList)) { return { configured: false, allowed: false } as const; } + // Check guild-level entries (e.g. "guild:123456") before user matching. + const guildId = params.guildId?.trim(); + if (guildId) { + for (const entry of rawAllowList) { + const text = String(entry).trim(); + if (text.startsWith("guild:") && text.slice("guild:".length) === guildId) { + return { configured: true, allowed: true } as const; + } + } + } const allowList = normalizeDiscordAllowList(rawAllowList.map(String), [ "discord:", "user:", @@ -325,6 +336,7 @@ async function resolveDiscordNativeAutocompleteAuthorized(params: { ? "channel" : "group", conversationId: rawChannelId || undefined, + guildId: interaction.guild?.id, }); const guildInfo = resolveDiscordGuildEntry({ guild: interaction.guild ?? undefined, @@ -706,6 +718,7 @@ async function dispatchDiscordCommandInteraction(params: { ? "channel" : "group", conversationId: rawChannelId || undefined, + guildId: interaction.guild?.id, }); const guildInfo = resolveDiscordGuildEntry({ guild: interaction.guild ?? undefined,