From e3606bac4df8704dd846cf0fb6b363f3280c9468 Mon Sep 17 00:00:00 2001 From: Sarah Fortune Date: Wed, 12 Aug 2026 19:49:02 -0700 Subject: [PATCH] fix(slack): preserve enterprise user identity scope --- extensions/slack/src/monitor/allow-list.ts | 2 +- extensions/slack/src/monitor/auth.ts | 63 ++++++++++++++++------ 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/extensions/slack/src/monitor/allow-list.ts b/extensions/slack/src/monitor/allow-list.ts index fece0dd143b5..932c83d9754d 100644 --- a/extensions/slack/src/monitor/allow-list.ts +++ b/extensions/slack/src/monitor/allow-list.ts @@ -152,7 +152,7 @@ export function resolveSlackUserAllowListForTeam(params: { try { const target = parseSlackTarget(entry); if (target?.kind === "user" && target.teamId?.toLowerCase() === teamId) { - return [target.id.toLowerCase()]; + return [entry]; } return params.preserveUnmatchedScopedEntries ? [entry] : []; } catch { diff --git a/extensions/slack/src/monitor/auth.ts b/extensions/slack/src/monitor/auth.ts index 6db7d52696bd..f243513fa038 100644 --- a/extensions/slack/src/monitor/auth.ts +++ b/extensions/slack/src/monitor/auth.ts @@ -48,6 +48,8 @@ const CHANNEL_MEMBERS_CACHE_MAX = 512; const SLACK_CHANNEL_ID = "slack"; const SLACK_USER_NAME_KIND = "plugin:slack-user-name" as const satisfies ChannelIngressIdentifierKind; +const SLACK_WORKSPACE_USER_ID_KIND = + "plugin:slack-workspace-user-id" as const satisfies ChannelIngressIdentifierKind; export class SlackSystemEventAuthRetryError extends Error {} function normalizeSlackUserId(raw?: string | null): string { @@ -66,7 +68,7 @@ function isSlackStableUserId(value: string): boolean { return /^[ubw][a-z0-9_]+$/i.test(value); } -function normalizeSlackStableEntry(entry: string): string | null { +function normalizeSlackWorkspaceUserEntry(entry: string): string | null { const normalized = entry.trim().toLowerCase(); if (!normalized) { return null; @@ -79,10 +81,22 @@ function normalizeSlackStableEntry(entry: string): string | null { } catch { return null; } + return null; +} + +function normalizeSlackBareUserEntry(entry: string): string | null { + const normalized = entry.trim().toLowerCase(); + if (!normalized || normalizeSlackWorkspaceUserEntry(normalized)) { + return null; + } const userId = normalizeSlackUserId(normalized); return isSlackStableUserId(userId) ? userId : null; } +function normalizeSlackStableEntry(entry: string): string | null { + return normalizeSlackBareUserEntry(entry) ?? normalizeSlackWorkspaceUserEntry(entry); +} + function normalizeSlackNameEntry(entry: string): string | null { const normalized = entry.trim().toLowerCase(); if (!normalized || normalizeSlackStableEntry(normalized)) { @@ -107,31 +121,46 @@ function normalizeSlackNameSlugEntry(entry: string): string | null { const slackIngressIdentity = defineStableChannelIngressIdentity({ key: "senderId", kind: "stable-id", - normalizeEntry: normalizeSlackStableEntry, + normalizeEntry: normalizeSlackBareUserEntry, normalizeSubject: normalizeSlackUserId, sensitivity: "pii", - aliases: ( - [ - ["senderName", normalizeSlackNameEntry], - ["senderNameSlug", normalizeSlackNameSlugEntry], - ] as const - ).map(([key, normalizeEntry]) => ({ - key, - kind: SLACK_USER_NAME_KIND, - normalizeEntry, - normalizeSubject: normalizeSlackNameSubject, - dangerous: true, - sensitivity: "pii" as const, - })), + aliases: [ + { + key: "workspaceSenderId", + kind: SLACK_WORKSPACE_USER_ID_KIND, + normalizeEntry: normalizeSlackWorkspaceUserEntry, + normalizeSubject: normalizeSlackWorkspaceUserEntry, + sensitivity: "pii", + }, + ...( + [ + ["senderName", normalizeSlackNameEntry], + ["senderNameSlug", normalizeSlackNameSlugEntry], + ] as const + ).map(([key, normalizeEntry]) => ({ + key, + kind: SLACK_USER_NAME_KIND, + normalizeEntry, + normalizeSubject: normalizeSlackNameSubject, + dangerous: true, + sensitivity: "pii" as const, + })), + ], }); -function createSlackIngressSubject(params: { senderId: string; senderName?: string }) { +function createSlackIngressSubject(params: { + senderId: string; + senderName?: string; + teamId?: string; +}) { const senderId = normalizeSlackUserId(params.senderId); + const teamId = normalizeOptionalLowercaseString(params.teamId); const senderName = params.senderName?.trim().toLowerCase(); const senderNameSlug = senderName ? normalizeSlackSlug(senderName) : undefined; return { stableId: senderId, aliases: { + workspaceSenderId: teamId && senderId ? `team:${teamId}:user:${senderId}` : undefined, senderName, senderNameSlug, }, @@ -394,6 +423,7 @@ export async function resolveSlackCommandIngress(params: { subject: createSlackIngressSubject({ senderId: params.senderId, senderName: params.senderName, + teamId, }), conversation: { kind: slackIngressConversationKind(params.channelType), @@ -474,6 +504,7 @@ async function decideSlackSystemIngress(params: { createSlackIngressSubject({ senderId: params.senderId, senderName, + teamId, }); const resolver = createSlackIngressResolver(params.ctx); const input: Parameters[0] = {