Skip to content

feat(auth): enforce domain and account bans on sign-in and workflow executions#4948

Merged
TheodoreSpeaks merged 7 commits into
stagingfrom
feat/ban-blocks-account
Jun 11, 2026
Merged

feat(auth): enforce domain and account bans on sign-in and workflow executions#4948
TheodoreSpeaks merged 7 commits into
stagingfrom
feat/ban-blocks-account

Conversation

@TheodoreSpeaks

@TheodoreSpeaks TheodoreSpeaks commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • domains in the appconfig blockedSignupDomains list now block existing accounts too, not just signups: sign-in is rejected across all providers (email/password, OAuth, SSO via a session.create.before gate) and every workflow execution is blocked
  • new blockedEmails list in the same appconfig profile (env fallback BLOCKED_EMAILS) bans specific email addresses with identical enforcement: signup, sign-in, executions, inbox
  • new lib/auth/ban.ts helper resolves effective ban status (active DB ban honoring banExpires, or blocked email/domain) in one query
  • preprocessExecution gains a Step 3.5 ban gate covering all execution entry points (manual, API, webhooks, schedules, deployed chats, resume/HITL, table columns, async jobs); checks the billing actor, the workflow owner, and the caller-provided user, fails closed on lookup errors
  • Mothership inbox tasks from blocked senders or for banned users are marked failed without running (and without emailing the suspended account)
  • moved isEmailInDenylist to access-control.ts so the ban helper doesn't pull the better-auth init into background workers
  • drive-by: fixed a pre-existing biome error in use-inline-rename.ts that was failing lint:check

Note: every domain already in the prod blocked-domains list starts hard-blocking its existing users on deploy. Already-signed-in users keep UI access for up to ~24h (session cookie cache) but executions/API keys/billing are cut immediately.

Type of Change

  • New feature

Testing

New unit tests for the ban helpers, access-control parsing, and the preprocessing gate (403, fail-closed 500, candidate-id dedup); 175 tests passing across touched dirs. bun run lint:check, tsc --noEmit, and check:api-validation:strict all green.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jun 11, 2026 5:50pm

Request Review

@cursor

cursor Bot commented Jun 10, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Changes authentication gates and blocks workflow execution for domains already on prod blocklists on deploy; misconfiguration or DB errors on ban checks fail closed and can suspend legitimate traffic.

Overview
Extends platform ban enforcement so blocked signup domains and a new per-address blockedEmails list (AppConfig / BLOCKED_EMAILS) apply to existing users, not only new signups.

Access control adds blockedEmails to config parsing and centralizes checks in isEmailBlockedByAccessControl (plus isEmailInDenylist, moved out of auth.ts). Auth uses that predicate on user create, email/password sign-in and sign-up, and adds a session.create.before hook so OAuth/SSO cannot open sessions when the account email is blocked.

lib/auth/ban.ts resolves effective blocks: active DB bans (respecting banExpires), list-based email/domain blocks, with getActivelyBannedUserIds and isEmailBlocked for callers that must not import full Better Auth.

Execution adds a preprocessing ban gate (step 3.5) that checks billing actor, caller userId, and workflow owner (403 on hit, fail-closed on DB errors). Mothership inbox fails tasks for blocked senders or banned/billed users without sending email replies.

Unit tests cover access-control, ban helpers, and preprocessing; auth.test.ts for denylist is removed in favor of access-control.test.ts.

Reviewed by Cursor Bugbot for commit f460845. Bugbot is set up for automated code reviews on this repo. Configure here.

@greptile-apps

greptile-apps Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a comprehensive ban-enforcement layer that blocks sign-in and workflow executions for email-address and domain-blocked accounts, in addition to the existing DB-level better-auth ban mechanism. A new lib/auth/ban.ts helper centralizes ban resolution, and the preprocessExecution pipeline gains a Step 3.5 gate covering all execution entry points with fail-closed semantics.

  • ban.ts: New helpers isBanActive, isEmailBlocked, and getActivelyBannedUserIds unify DB-ban checks with access-control-config (email/domain) checks in a single query, propagating errors so callers fail closed.
  • auth.ts / session gate: A new session.create.before hook blocks session creation for any email/domain on the blocklist across all providers (email, OAuth, SSO); placed outside the inner try so errors propagate rather than being swallowed.
  • preprocessing.ts Step 3.5: After resolving the billing actor, the ban gate checks the billed account, the workflow owner, and the caller-supplied user ID in one query before any billing or rate-limit work proceeds.
  • inbox/executor.ts: Directly checks the sender email (isEmailBlocked) and the resolved/billed user IDs (getActivelyBannedUserIds) with fail-closed error handling; the responseSent flag is set before markTaskFailed to prevent emailing suspended accounts on any path.

Confidence Score: 5/5

Safe to merge. The ban gates are fail-closed across all execution paths, the session hook correctly propagates errors, and the inbox executor suppresses error emails on every banned-user exit path including infrastructure failures.

The changes are additive security guards with no modifications to existing happy-path logic. Each new gate consistently fails closed on lookup errors, is covered by dedicated unit tests including failure propagation and edge cases, and matches the pattern used by the surrounding code. No execution path was found that could bypass the ban check once a block is in effect.

No files require special attention. All changed files implement the new ban policy consistently.

Important Files Changed

Filename Overview
apps/sim/lib/auth/ban.ts New helper module cleanly centralises DB-ban + access-control checks; correctly filters falsy IDs, propagates DB errors to callers, and handles subdomain matching.
apps/sim/lib/auth/access-control.ts Adds blockedEmails field and two new exported predicates; backward-compatible with existing AppConfig profiles and env-var fallback.
apps/sim/lib/auth/auth.ts Session gate correctly placed outside inner try-catch so APIError propagates; email/password path also blocked via before-middleware; OAuth/SSO covered by session.create.before.
apps/sim/lib/execution/preprocessing.ts Step 3.5 ban gate checks billing actor, caller userId, and workflow owner in one batched query; fail-closed catch returns 500 before any billing/rate-limit work proceeds.
apps/sim/lib/mothership/inbox/executor.ts Ban check correctly combines isEmailBlocked (sender raw email) with getActivelyBannedUserIds (resolved user + billed account); responseSent set before markTaskFailed on all ban paths.
apps/sim/lib/auth/ban.test.ts Comprehensive unit tests covering active/expired bans, subdomain matching, email blocklist, DB failure propagation, and deduplication.
apps/sim/lib/execution/preprocessing.test.ts Ban gate tests cover 403 block, fail-closed 500, candidate-ID deduplication, and unknown sentinel exclusion.
apps/sim/lib/core/config/env.ts Adds optional BLOCKED_EMAILS env var with inline documentation; consistent with existing env-var patterns.

Sequence Diagram

sequenceDiagram
    participant C as Client
    participant A as auth.ts
    participant S as session.create.before
    participant P as preprocessExecution
    participant B as ban.ts
    participant DB as Database

    Note over C,DB: Sign-in (email/password)
    C->>A: POST /sign-in/email
    A->>A: isEmailBlockedByAccessControl(requestEmail)
    alt email blocked
        A-->>C: 403 FORBIDDEN
    else
        A->>S: session.create.before
        S->>DB: "SELECT email WHERE id=session.userId"
        S->>S: isEmailBlockedByAccessControl
        alt blocked
            S-->>C: 403 FORBIDDEN
        else
            S-->>C: Session created
        end
    end

    Note over C,DB: Workflow execution
    C->>P: preprocessExecution
    P->>B: getActivelyBannedUserIds([actor, userId, owner])
    B->>DB: SELECT ban fields WHERE id IN (...)
    alt any banned
        P-->>C: 403 Account suspended
    else DB error
        P-->>C: 500 fail-closed
    else clean
        P-->>C: success
    end

    Note over C,DB: Inbox execution
    C->>P: executeInboxTask
    P->>B: isEmailBlocked(senderEmail)
    B->>DB: "SELECT ban WHERE lower(email)=sender"
    P->>B: getActivelyBannedUserIds([userId, billedId])
    alt blocked
        P-->>C: markTaskFailed (no email)
    else DB error
        P-->>C: markTaskFailed fail-closed
    else clean
        P->>P: run orchestrator
    end
Loading

Reviews (5): Last reviewed commit: "fix(mothership): ban-check the workspace..." | Re-trigger Greptile

Comment thread apps/sim/lib/mothership/inbox/executor.ts Outdated
@greptile-apps

greptile-apps Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR enforces domain bans and account bans across all sign-in paths and workflow execution entry points. A new ban.ts helper centralises ban resolution (active DB ban + blocked email domain) in a single cacheable query, and a session.create.before hook extends domain blocking from signup-only to all providers including OAuth/SSO.

  • Auth layer: session.create.before now blocks domain-banned accounts from creating sessions (OAuth/SSO path); the existing before hook is extended to reject sign-in (not just sign-up) for email/password with a blocked domain.
  • Execution layer: preprocessExecution gains a Step 3.5 ban gate that checks both the billing actor and the caller-provided userId before any billing or rate-limit work, failing closed with 500 on DB errors.
  • Inbox executor: Inbox tasks for banned users are marked failed without running, and no response email is sent — with one edge case noted below.

Confidence Score: 3/5

The auth and preprocessing layers are solid, but the inbox executor has a defect where a DB failure during the ban check causes the outer catch to send an error email to a sender who may be banned — directly contradicting the stated design intent.

The core ban logic in ban.ts, the preprocessing gate, and the auth-layer hooks are well-designed and correctly fail-closed. The issue is in executor.ts: the ban check lives inside the outer try block, so any DB error during that check propagates to the catch handler which then calls sendInboxResponse if no response has been sent yet. The code comment explicitly calls out that no email should ever be sent to a suspended account, but a transient DB outage breaks that guarantee silently.

apps/sim/lib/mothership/inbox/executor.ts needs the most attention — the ban-check placement inside the try block allows the catch handler to email potentially-banned senders on DB failure.

Important Files Changed

Filename Overview
apps/sim/lib/auth/ban.ts New helper: isBanActive (expiry-aware) + getActivelyBannedUserIds (single DB query, fails closed). Logic is correct and well-tested.
apps/sim/lib/auth/auth.ts Adds domain-block check to session.create.before (OAuth/SSO path) and extends the before-hook to block sign-in (not just sign-up) for email/password. The deliberate placement outside the inner try is correct and well-commented.
apps/sim/lib/execution/preprocessing.ts Adds Step 3.5 ban gate after billing actor is resolved; checks both billing actor and caller-provided userId, deduplicates, skips 'unknown' sentinel, and fails closed with 500 on DB error. Coverage and fail-closed semantics look correct.
apps/sim/lib/mothership/inbox/executor.ts Ban check added after task is claimed. Two issues: (1) ban-check DB failure causes the outer catch to send an error email to the sender, violating 'never mail a suspended account'; (2) non-member external senders with banned accounts slip through because resolveUserId falls back to ws.ownerId.
apps/sim/lib/auth/access-control.ts isEmailInDenylist moved here from auth.ts to decouple background workers from better-auth init. Logic unchanged and well-tested.

Sequence Diagram

sequenceDiagram
    participant C as Client / Email Sender
    participant Auth as Auth Layer (auth.ts)
    participant Pre as preprocessExecution
    participant Inbox as InboxExecutor
    participant Ban as getActivelyBannedUserIds
    participant DB as Database

    Note over Auth: Email/password sign-in
    C->>Auth: POST /sign-in/email
    Auth->>Auth: isEmailInDenylist(requestEmail)
    alt domain blocked
        Auth-->>C: 403 FORBIDDEN
    end

    Note over Auth: OAuth/SSO sign-in
    C->>Auth: OAuth callback
    Auth->>Auth: session.create.before hook
    Auth->>DB: "SELECT email WHERE userId = session.userId"
    Auth->>Auth: isEmailInDenylist(email, blockedDomains)
    alt domain blocked
        Auth-->>C: 403 FORBIDDEN
    end

    Note over Pre: Any workflow execution
    C->>Pre: preprocessExecution(options)
    Pre->>Pre: Step 3 resolve actorUserId
    Pre->>Ban: getActivelyBannedUserIds([actorUserId, userId])
    Ban->>DB: SELECT id,email,banned,banExpires
    alt banned
        Pre-->>C: success false statusCode 403
    else DB error
        Pre-->>C: success false statusCode 500
    end

    Note over Inbox: Inbox task execution
    C->>Inbox: executeInboxTask(taskId)
    Inbox->>Inbox: claim task + resolveUserId
    Inbox->>Ban: getActivelyBannedUserIds([userId])
    alt banned
        Inbox->>DB: markTaskFailed no email sent
    else DB error catch block
        Inbox->>C: error email sent unintended
    end
Loading

Reviews (2): Last reviewed commit: "feat(auth): enforce domain and account b..." | Re-trigger Greptile

Comment thread apps/sim/lib/mothership/inbox/executor.ts Outdated
Comment thread apps/sim/lib/mothership/inbox/executor.ts Outdated
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

Comment thread apps/sim/lib/mothership/inbox/executor.ts
Comment thread apps/sim/lib/execution/preprocessing.ts
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

…count

# Conflicts:
#	apps/sim/hooks/use-inline-rename.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5e5c03e. Configure here.

Comment thread apps/sim/lib/mothership/inbox/executor.ts
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

@TheodoreSpeaks TheodoreSpeaks merged commit 5fb37b4 into staging Jun 11, 2026
15 checks passed
@TheodoreSpeaks TheodoreSpeaks deleted the feat/ban-blocks-account branch June 11, 2026 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant