Skip to content

Commit 3188ee9

Browse files
fix(mothership): fail inbox ban checks closed without emailing, gate blocked senders
1 parent 21d7927 commit 3188ee9

3 files changed

Lines changed: 57 additions & 7 deletions

File tree

apps/sim/lib/auth/ban.test.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ vi.mock('@/lib/core/config/env', () => ({
2121
}))
2222
vi.mock('@/lib/core/config/feature-flags', () => ({ isAppConfigEnabled: false }))
2323

24-
import { getActivelyBannedUserIds, isBanActive } from '@/lib/auth/ban'
24+
import { getActivelyBannedUserIds, isBanActive, isEmailDomainBlocked } from '@/lib/auth/ban'
2525

2626
describe('isBanActive', () => {
2727
it('returns true for a permanent ban', () => {
@@ -42,6 +42,23 @@ describe('isBanActive', () => {
4242
})
4343
})
4444

45+
describe('isEmailDomainBlocked', () => {
46+
beforeEach(() => {
47+
envRef.BLOCKED_SIGNUP_DOMAINS = 'bad.com'
48+
})
49+
50+
it('returns true for blocked domains and subdomains', async () => {
51+
expect(await isEmailDomainBlocked('a@bad.com')).toBe(true)
52+
expect(await isEmailDomainBlocked('a@mail.bad.com')).toBe(true)
53+
})
54+
55+
it('returns false for clean domains and missing emails', async () => {
56+
expect(await isEmailDomainBlocked('a@good.com')).toBe(false)
57+
expect(await isEmailDomainBlocked(null)).toBe(false)
58+
expect(await isEmailDomainBlocked(undefined)).toBe(false)
59+
})
60+
})
61+
4562
describe('getActivelyBannedUserIds', () => {
4663
beforeEach(() => {
4764
vi.clearAllMocks()

apps/sim/lib/auth/ban.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ export function isBanActive(row: { banned: boolean | null; banExpires: Date | nu
1212
return true
1313
}
1414

15+
/**
16+
* True when the email's domain is in the appconfig blocked-domains list.
17+
* For gating raw emails (e.g. inbound senders) that have no user row.
18+
*/
19+
export async function isEmailDomainBlocked(email: string | null | undefined): Promise<boolean> {
20+
if (!email) return false
21+
const accessControl = await getAccessControlConfig()
22+
return isEmailInDenylist(email, accessControl.blockedSignupDomains)
23+
}
24+
1525
/**
1626
* Returns the subset of the given user ids that are currently blocked: an
1727
* active account ban, or an email domain in the appconfig blocked-domains

apps/sim/lib/mothership/inbox/executor.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { createLogger } from '@sim/logger'
33
import { getErrorMessage } from '@sim/utils/errors'
44
import { generateId } from '@sim/utils/id'
55
import { and, eq, sql } from 'drizzle-orm'
6-
import { getActivelyBannedUserIds } from '@/lib/auth/ban'
6+
import { getActivelyBannedUserIds, isEmailDomainBlocked } from '@/lib/auth/ban'
77
import { resolveOrCreateChat } from '@/lib/copilot/chat/lifecycle'
88
import { appendCopilotChatMessages } from '@/lib/copilot/chat/messages-store'
99
import { buildIntegrationToolSchemas } from '@/lib/copilot/chat/payload'
@@ -94,11 +94,34 @@ export async function executeInboxTask(taskId: string): Promise<void> {
9494
return
9595
}
9696

97-
// No email response on purpose — never mail a suspended account.
98-
const [bannedUserId] = await getActivelyBannedUserIds([userId])
99-
if (bannedUserId) {
100-
logger.warn('Blocking inbox task: resolved user is banned', { taskId, userId })
101-
await markTaskFailed(taskId, 'User account is suspended')
97+
// Blocked senders and banned accounts must not drive the agent; the sender
98+
// email is checked directly because non-members resolve to the workspace
99+
// owner. Fails closed on lookup errors. No email response in any of these
100+
// paths — never mail a suspended account.
101+
let blockReason: string | null = null
102+
try {
103+
const [senderBlocked, [bannedUserId]] = await Promise.all([
104+
isEmailDomainBlocked(inboxTask.fromEmail),
105+
getActivelyBannedUserIds([userId]),
106+
])
107+
if (senderBlocked || bannedUserId) {
108+
logger.warn('Blocking inbox task: sender or resolved user is banned', {
109+
taskId,
110+
userId,
111+
senderBlocked,
112+
})
113+
blockReason = 'User account is suspended'
114+
}
115+
} catch (error) {
116+
logger.error('Inbox task ban check failed; failing closed', {
117+
taskId,
118+
error: getErrorMessage(error, 'Unknown error'),
119+
})
120+
blockReason = 'Unable to verify account status'
121+
}
122+
if (blockReason) {
123+
responseSent = true
124+
await markTaskFailed(taskId, blockReason)
102125
return
103126
}
104127

0 commit comments

Comments
 (0)