fix(security): gate slash commands by sender

This commit is contained in:
Peter Steinberger
2026-01-17 05:25:37 +00:00
parent c8b826ea8c
commit a624878973
14 changed files with 525 additions and 85 deletions

View File

@@ -22,7 +22,6 @@ import {
normalizeDiscordAllowList,
normalizeDiscordSlug,
resolveDiscordChannelConfig,
resolveDiscordCommandAuthorized,
resolveDiscordGuildEntry,
resolveDiscordShouldRequireMention,
resolveDiscordUserAllowed,
@@ -314,18 +313,38 @@ export async function preflightDiscordMessage(
(message.mentionedUsers?.length ?? 0) > 0 ||
(message.mentionedRoles?.length ?? 0) > 0),
);
if (!isDirectMessage) {
commandAuthorized = resolveDiscordCommandAuthorized({
isDirectMessage,
allowFrom: params.allowFrom,
guildInfo,
author,
});
}
const allowTextCommands = shouldHandleTextCommands({
cfg: params.cfg,
surface: "discord",
});
if (!isDirectMessage) {
const ownerAllowList = normalizeDiscordAllowList(params.allowFrom, ["discord:", "user:"]);
const ownerOk = ownerAllowList
? allowListMatches(ownerAllowList, {
id: author.id,
name: author.username,
tag: formatDiscordUserTag(author),
})
: false;
const channelUsers = channelConfig?.users ?? guildInfo?.users;
const usersOk =
Array.isArray(channelUsers) && channelUsers.length > 0
? resolveDiscordUserAllowed({
allowList: channelUsers,
userId: author.id,
userName: author.username,
userTag: formatDiscordUserTag(author),
})
: false;
commandAuthorized = ownerOk || usersOk;
if (allowTextCommands && hasControlCommand(baseText, params.cfg) && !commandAuthorized) {
logVerbose(`Blocked discord control command from unauthorized sender ${author.id}`);
return null;
}
}
const shouldBypassMention =
allowTextCommands &&
isGuildMessage &&

View File

@@ -423,6 +423,18 @@ async function dispatchDiscordCommandInteraction(params: {
const isGroupDm = channelType === ChannelType.GroupDM;
const channelName = channel && "name" in channel ? (channel.name as string) : undefined;
const channelSlug = channelName ? normalizeDiscordSlug(channelName) : "";
const ownerAllowList = normalizeDiscordAllowList(discordConfig?.dm?.allowFrom ?? [], [
"discord:",
"user:",
]);
const ownerOk =
ownerAllowList && user
? allowListMatches(ownerAllowList, {
id: user.id,
name: user.username,
tag: formatDiscordUserTag(user),
})
: false;
const guildInfo = resolveDiscordGuildEntry({
guild: interaction.guild ?? undefined,
guildEntries: discordConfig?.guilds,
@@ -508,17 +520,19 @@ async function dispatchDiscordCommandInteraction(params: {
}
if (!isDirectMessage) {
const channelUsers = channelConfig?.users ?? guildInfo?.users;
if (Array.isArray(channelUsers) && channelUsers.length > 0) {
const userOk = resolveDiscordUserAllowed({
allowList: channelUsers,
userId: user.id,
userName: user.username,
userTag: formatDiscordUserTag(user),
});
if (!userOk) {
await respond("You are not authorized to use this command.");
return;
}
const hasUserAllowlist = Array.isArray(channelUsers) && channelUsers.length > 0;
const userOk = hasUserAllowlist
? resolveDiscordUserAllowed({
allowList: channelUsers,
userId: user.id,
userName: user.username,
userTag: formatDiscordUserTag(user),
})
: false;
commandAuthorized = useAccessGroups ? ownerOk || userOk : hasUserAllowlist ? userOk : true;
if (!commandAuthorized) {
await respond("You are not authorized to use this command.", { ephemeral: true });
return;
}
}
if (isGroupDm && discordConfig?.dm?.groupEnabled === false) {