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

@@ -26,7 +26,7 @@ import { sendMessageSlack } from "../../send.js";
import type { SlackMessageEvent } from "../../types.js";
import { allowListMatches, resolveSlackUserAllowed } from "../allow-list.js";
import { isSlackSenderAllowListed, resolveSlackEffectiveAllowFrom } from "../auth.js";
import { resolveSlackEffectiveAllowFrom } from "../auth.js";
import { resolveSlackChannelConfig } from "../channel-config.js";
import { normalizeSlackChannelType, type SlackMonitorContext } from "../context.js";
import { resolveSlackMedia, resolveSlackThreadStarter } from "../media.js";
@@ -217,18 +217,34 @@ export async function prepareSlackMessage(params: {
return null;
}
const commandAuthorized =
isSlackSenderAllowListed({
allowListLower: allowFromLower,
senderId,
senderName,
}) && channelUserAuthorized;
const hasAnyMention = /<@[^>]+>/.test(message.text ?? "");
const allowTextCommands = shouldHandleTextCommands({
cfg,
surface: "slack",
});
const ownerAuthorized = allowListMatches({
allowList: allowFromLower,
id: senderId,
name: senderName,
});
const channelUsersAllowlistConfigured =
isRoom && Array.isArray(channelConfig?.users) && channelConfig.users.length > 0;
const channelCommandAuthorized =
isRoom && channelUsersAllowlistConfigured
? resolveSlackUserAllowed({
allowList: channelConfig?.users,
userId: senderId,
userName: senderName,
})
: false;
const commandAuthorized = ownerAuthorized || channelCommandAuthorized;
if (allowTextCommands && isRoomish && hasControlCommand(message.text ?? "", cfg) && !commandAuthorized) {
logVerbose(`Blocked slack control command from unauthorized sender ${senderId}`);
return null;
}
const shouldRequireMention = isRoom
? (channelConfig?.requireMention ?? ctx.defaultRequireMention)
: false;

View File

@@ -167,6 +167,7 @@ export function registerSlackMonitorSlashCommands(params: {
const isDirectMessage = channelType === "im";
const isGroupDm = channelType === "mpim";
const isRoom = channelType === "channel" || channelType === "group";
const isRoomish = isRoom || isGroupDm;
if (
!ctx.isChannelAllowed({
@@ -270,14 +271,16 @@ export function registerSlackMonitorSlashCommands(params: {
const sender = await ctx.resolveUserName(command.user_id);
const senderName = sender?.name ?? command.user_name ?? command.user_id;
const channelUserAllowed = isRoom
const channelUsersAllowlistConfigured =
isRoom && Array.isArray(channelConfig?.users) && channelConfig.users.length > 0;
const channelUserAllowed = channelUsersAllowlistConfigured
? resolveSlackUserAllowed({
allowList: channelConfig?.users,
userId: command.user_id,
userName: senderName,
})
: true;
if (isRoom && !channelUserAllowed) {
: false;
if (channelUsersAllowlistConfigured && !channelUserAllowed) {
await respond({
text: "You are not authorized to use this command here.",
response_type: "ephemeral",
@@ -285,6 +288,22 @@ export function registerSlackMonitorSlashCommands(params: {
return;
}
const ownerAllowed = allowListMatches({
allowList: effectiveAllowFromLower,
id: command.user_id,
name: senderName,
});
if (isRoomish && ctx.useAccessGroups && !(ownerAllowed || channelUserAllowed)) {
await respond({
text: "You are not authorized to use this command.",
response_type: "ephemeral",
});
return;
}
if (isRoomish) {
commandAuthorized = ctx.useAccessGroups ? ownerAllowed || channelUserAllowed : true;
}
if (commandDefinition && supportsInteractiveArgMenus) {
const menu = resolveCommandArgMenu({
command: commandDefinition,
@@ -313,7 +332,6 @@ export function registerSlackMonitorSlashCommands(params: {
const channelName = channelInfo?.name;
const roomLabel = channelName ? `#${channelName}` : `#${command.channel_id}`;
const isRoomish = isRoom || isGroupDm;
const route = resolveAgentRoute({
cfg,
channel: "slack",