Discord: fix allowlist gating. Closes #961

This commit is contained in:
Shadow
2026-01-15 11:53:13 -06:00
parent 4de81ed6c4
commit 316e8b2eb2
7 changed files with 29 additions and 6 deletions

View File

@@ -8,6 +8,7 @@
- Browser: extension mode recovers when only one tab is attached (stale targetId fallback). - Browser: extension mode recovers when only one tab is attached (stale targetId fallback).
- Browser: fix `tab not found` for extension relay snapshots/actions when Playwright blocks `newCDPSession` (use the single available Page). - Browser: fix `tab not found` for extension relay snapshots/actions when Playwright blocks `newCDPSession` (use the single available Page).
- Telegram: add bidirectional reaction support with configurable notifications and agent guidance. (#964) — thanks @bohdanpodvirnyi. - Telegram: add bidirectional reaction support with configurable notifications and agent guidance. (#964) — thanks @bohdanpodvirnyi.
- Discord: allow allowlisted guilds without channel lists to receive messages when `groupPolicy="allowlist"`. — thanks @thewilloftheshadow.
## 2026.1.14-1 ## 2026.1.14-1

View File

@@ -191,7 +191,7 @@ Notes:
- Your config requires mentions and you didnt mention it, or - Your config requires mentions and you didnt mention it, or
- Your guild/channel allowlist denies the channel/user. - Your guild/channel allowlist denies the channel/user.
- **`requireMention: false` but still no replies**: - **`requireMention: false` but still no replies**:
- `channels.discord.groupPolicy` defaults to **allowlist**; set it to `"open"` or explicitly list channels under `channels.discord.guilds.<id>.channels`. - `channels.discord.groupPolicy` defaults to **allowlist**; set it to `"open"` or add a guild entry under `channels.discord.guilds` (optionally list channels under `channels.discord.guilds.<id>.channels` to restrict).
- `requireMention` must live under `channels.discord.guilds` (or a specific channel). `channels.discord.requireMention` at the top level is ignored. - `requireMention` must live under `channels.discord.guilds` (or a specific channel). `channels.discord.requireMention` at the top level is ignored.
- **Permission audits** (`channels status --probe`) only check numeric channel IDs. If you use slugs/names as `channels.discord.guilds.*.channels` keys, the audit cant verify permissions. - **Permission audits** (`channels status --probe`) only check numeric channel IDs. If you use slugs/names as `channels.discord.guilds.*.channels` keys, the audit cant verify permissions.
- **DMs dont work**: `channels.discord.dm.enabled=false`, `channels.discord.dm.policy="disabled"`, or you havent been approved yet (`channels.discord.dm.policy="pairing"`). - **DMs dont work**: `channels.discord.dm.enabled=false`, `channels.discord.dm.policy="disabled"`, or you havent been approved yet (`channels.discord.dm.policy="pairing"`).
@@ -352,6 +352,7 @@ Allowlist matching notes:
- Prefixes like `discord:`/`user:` (users) and `channel:` (group DMs) are supported. - Prefixes like `discord:`/`user:` (users) and `channel:` (group DMs) are supported.
- Use `*` to allow any sender/channel. - Use `*` to allow any sender/channel.
- When `guilds.<id>.channels` is present, channels not listed are denied by default. - When `guilds.<id>.channels` is present, channels not listed are denied by default.
- When `guilds.<id>.channels` is omitted, all channels in the allowlisted guild are allowed.
Native command notes: Native command notes:
- The registered commands mirror Clawdbots chat commands. - The registered commands mirror Clawdbots chat commands.

View File

@@ -1542,10 +1542,11 @@ See [Streaming](/concepts/streaming).
### Discord doesnt reply in my server even with `requireMention: false`. Why? ### Discord doesnt reply in my server even with `requireMention: false`. Why?
`requireMention` only controls mentiongating **after** the channel passes allowlists. `requireMention` only controls mentiongating **after** the channel passes allowlists.
By default `channels.discord.groupPolicy` is **allowlist**, so guild channels must be explicitly enabled. By default `channels.discord.groupPolicy` is **allowlist**, so guilds must be explicitly enabled.
If you set `channels.discord.guilds.<guildId>.channels`, only the listed channels are allowed; omit it to allow all channels in the guild.
Fix checklist: Fix checklist:
1) Set `channels.discord.groupPolicy: "open"` **or** add the guild/channel allowlist. 1) Set `channels.discord.groupPolicy: "open"` **or** add a guild allowlist entry (and optionally a channel allowlist).
2) Use **numeric channel IDs** in `channels.discord.guilds.<guildId>.channels`. 2) Use **numeric channel IDs** in `channels.discord.guilds.<guildId>.channels`.
3) Put `requireMention: false` **under** `channels.discord.guilds` (global or perchannel). 3) Put `requireMention: false` **under** `channels.discord.guilds` (global or perchannel).
Toplevel `channels.discord.requireMention` is not a supported key. Toplevel `channels.discord.requireMention` is not a supported key.

View File

@@ -215,6 +215,7 @@ describe("discord groupPolicy gating", () => {
expect( expect(
isDiscordGroupAllowedByPolicy({ isDiscordGroupAllowedByPolicy({
groupPolicy: "open", groupPolicy: "open",
guildAllowlisted: false,
channelAllowlistConfigured: false, channelAllowlistConfigured: false,
channelAllowed: false, channelAllowed: false,
}), }),
@@ -225,26 +226,40 @@ describe("discord groupPolicy gating", () => {
expect( expect(
isDiscordGroupAllowedByPolicy({ isDiscordGroupAllowedByPolicy({
groupPolicy: "disabled", groupPolicy: "disabled",
guildAllowlisted: true,
channelAllowlistConfigured: true, channelAllowlistConfigured: true,
channelAllowed: true, channelAllowed: true,
}), }),
).toBe(false); ).toBe(false);
}); });
it("blocks allowlist when no channel allowlist configured", () => { it("blocks allowlist when guild is not allowlisted", () => {
expect( expect(
isDiscordGroupAllowedByPolicy({ isDiscordGroupAllowedByPolicy({
groupPolicy: "allowlist", groupPolicy: "allowlist",
guildAllowlisted: false,
channelAllowlistConfigured: false, channelAllowlistConfigured: false,
channelAllowed: true, channelAllowed: true,
}), }),
).toBe(false); ).toBe(false);
}); });
it("allows allowlist when guild allowlisted but no channel allowlist", () => {
expect(
isDiscordGroupAllowedByPolicy({
groupPolicy: "allowlist",
guildAllowlisted: true,
channelAllowlistConfigured: false,
channelAllowed: true,
}),
).toBe(true);
});
it("allows allowlist when channel is allowed", () => { it("allows allowlist when channel is allowed", () => {
expect( expect(
isDiscordGroupAllowedByPolicy({ isDiscordGroupAllowedByPolicy({
groupPolicy: "allowlist", groupPolicy: "allowlist",
guildAllowlisted: true,
channelAllowlistConfigured: true, channelAllowlistConfigured: true,
channelAllowed: true, channelAllowed: true,
}), }),
@@ -255,6 +270,7 @@ describe("discord groupPolicy gating", () => {
expect( expect(
isDiscordGroupAllowedByPolicy({ isDiscordGroupAllowedByPolicy({
groupPolicy: "allowlist", groupPolicy: "allowlist",
guildAllowlisted: true,
channelAllowlistConfigured: true, channelAllowlistConfigured: true,
channelAllowed: false, channelAllowed: false,
}), }),

View File

@@ -197,13 +197,15 @@ export function resolveDiscordShouldRequireMention(params: {
export function isDiscordGroupAllowedByPolicy(params: { export function isDiscordGroupAllowedByPolicy(params: {
groupPolicy: "open" | "disabled" | "allowlist"; groupPolicy: "open" | "disabled" | "allowlist";
guildAllowlisted: boolean;
channelAllowlistConfigured: boolean; channelAllowlistConfigured: boolean;
channelAllowed: boolean; channelAllowed: boolean;
}): boolean { }): boolean {
const { groupPolicy, channelAllowlistConfigured, channelAllowed } = params; const { groupPolicy, guildAllowlisted, channelAllowlistConfigured, channelAllowed } = params;
if (groupPolicy === "disabled") return false; if (groupPolicy === "disabled") return false;
if (groupPolicy === "open") return true; if (groupPolicy === "open") return true;
if (!channelAllowlistConfigured) return false; if (!guildAllowlisted) return false;
if (!channelAllowlistConfigured) return true;
return channelAllowed; return channelAllowed;
} }

View File

@@ -260,6 +260,7 @@ export async function preflightDiscordMessage(
isGuildMessage && isGuildMessage &&
!isDiscordGroupAllowedByPolicy({ !isDiscordGroupAllowedByPolicy({
groupPolicy: params.groupPolicy, groupPolicy: params.groupPolicy,
guildAllowlisted: Boolean(guildInfo),
channelAllowlistConfigured, channelAllowlistConfigured,
channelAllowed, channelAllowed,
}) })

View File

@@ -448,6 +448,7 @@ async function dispatchDiscordCommandInteraction(params: {
const channelAllowed = channelConfig?.allowed !== false; const channelAllowed = channelConfig?.allowed !== false;
const allowByPolicy = isDiscordGroupAllowedByPolicy({ const allowByPolicy = isDiscordGroupAllowedByPolicy({
groupPolicy: discordConfig?.groupPolicy ?? "open", groupPolicy: discordConfig?.groupPolicy ?? "open",
guildAllowlisted: Boolean(guildInfo),
channelAllowlistConfigured, channelAllowlistConfigured,
channelAllowed, channelAllowed,
}); });