Skip to content

improvement(auth): layer disposable-email-domains into signup email validation#5010

Merged
TheodoreSpeaks merged 3 commits into
stagingfrom
investigate/botters
Jun 12, 2026
Merged

improvement(auth): layer disposable-email-domains into signup email validation#5010
TheodoreSpeaks merged 3 commits into
stagingfrom
investigate/botters

Conversation

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator

Summary

  • Add the disposable-email-domains package (exact + wildcard lists) as an extra signup gate, composed into better-auth-harmony's validator alongside its bundled Mailchecker list — an email is rejected if either flags it (union, not replacement).
  • New server-only isDisposableEmailDomain() module + ambient types; kept out of the client bundle so the ~120K dataset never ships to the browser. Client quickValidateEmail keeps its small fast-feedback set.
  • Still gated behind the existing SIGNUP_EMAIL_VALIDATION_ENABLED flag.

Type of Change

  • Improvement (defense-in-depth)

Testing

  • New unit tests for isDisposableEmailDomain (exact, wildcard subdomain, case-insensitivity, normal domain, custom catch-all, malformed) — all passing
  • bun run lint clean, bun run check:api-validation:strict passed, tsc --noEmit 0 errors

Note

  • This is the generic-throwaway-service layer; it does not block privately-registered catch-all domains (e.g. the recent botnet) — those still need BLOCKED_SIGNUP_DOMAINS / BLOCKED_EMAIL_MX_HOSTS.

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 and others added 2 commits June 12, 2026 10:45
…me/drop prompt

`drizzle-kit push --force` only suppresses the data-loss confirm, not the
rename-vs-drop disambiguation prompt. That prompt fires whenever a diff both
adds and drops tables/columns at once (e.g. migration 0231 created
sim_trigger_state while dropping the workspace_notification_* tables), and in
CI it crashes with a bare "Interactive prompts require a TTY" stack trace.

Catch that specific failure in the dev push step and emit a GitHub error
annotation explaining the cause and the fix (drop the stale objects on the dev
DB to match schema.ts — the same DROPs the versioned migration already applied
to staging/prod), instead of leaving an opaque trace. Exit status is preserved
either way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…alidation

Compose the disposable-email-domains list (exact + wildcard) into better-auth-harmony's validator alongside its bundled Mailchecker list, so signup rejects an email if either flags it. Server-only module to keep the dataset out of the client bundle.
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

@cursor

cursor Bot commented Jun 12, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes signup acceptance logic on the auth path when SIGNUP_EMAIL_VALIDATION_ENABLED is on; false positives could block legitimate signups, though behavior is additive defense-in-depth rather than replacing existing checks.

Overview
Signup email validation now rejects addresses when either Mailchecker (via better-auth-harmony) or the disposable-email-domains dataset flags the domain, by wiring a custom emailHarmony validator instead of the default plugin-only check. A new server-only isDisposableEmailDomain() helper lazy-loads ~120K exact domains plus wildcard bases (dynamic import + cache) so the list never ships to the client; unit tests and ambient module types are included. The disposable-email-domains dependency is added to apps/sim.

Separately, the dev migrations workflow captures db:push output and emits a GitHub Actions error when drizzle-kit fails on the non-interactive rename/drop TTY prompt, with guidance to reconcile dev DB drift against schema.ts/versioned migrations.

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

@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 a489c4f. Configure here.

Comment thread apps/sim/lib/auth/auth.ts
@greptile-apps

greptile-apps Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR layers the disposable-email-domains package (~120 K domain dataset) into the signup email validation pipeline as a union with the existing better-auth-harmony Mailchecker check, so an email is rejected if either list flags it. The new isDisposableEmailDomain module is lazily loaded and memoized server-side, gated behind the existing SIGNUP_EMAIL_VALIDATION_ENABLED flag.

  • Validator composition (auth.ts): The custom validator correctly calls validateEmailWithMailchecker (the full default — validator.js format check + Mailchecker domain block) first, then additionally checks isDisposableEmailDomain; no existing validation is dropped.
  • Lazy cache (disposable-domains.server.ts): The dataset is loaded on first use via dynamic import() and memoized in a module-level variable; ES module caching prevents duplicate I/O even under concurrent cold-start requests.
  • Migrations workflow (.github/workflows/migrations.yml): Output is now captured and a descriptive GitHub Actions error annotation is emitted when drizzle-kit hits an interactive TTY prompt in CI, replacing an opaque stack trace.

Confidence Score: 5/5

Safe to merge — the new validation layer is additive and gated behind an existing feature flag.

The validator composition in auth.ts preserves the full original check (format validation via validator.js + Mailchecker domain block) and adds the disposable-email-domains list on top. The lazy-load cache is correct and idiomatic. Test coverage is thorough. The migrations workflow change is a pure diagnostic improvement with no behavioral change on success paths.

No files require special attention.

Important Files Changed

Filename Overview
apps/sim/lib/auth/auth.ts Correctly composes validateEmailWithMailchecker (format check + Mailchecker) with isDisposableEmailDomain as a union validator; original format and Mailchecker checks are preserved.
apps/sim/lib/messaging/email/disposable-domains.server.ts New server-only module that lazily loads the disposable-email-domains dataset and memoizes it; logic is correct, wildcard and exact checks both work as intended.
apps/sim/lib/messaging/email/disposable-domains.server.test.ts 7 test cases covering exact match, wildcard subdomain, bare wildcard base, case-insensitivity, normal provider, custom catch-all, and malformed input — all branches exercised.
apps/sim/lib/messaging/email/disposable-domains.d.ts Ambient type declarations for the untyped disposable-email-domains package; correct shape matching the actual module exports.
.github/workflows/migrations.yml Replaces bare db:push --force with output-captured version that emits an actionable GitHub Actions error annotation when drizzle hits a TTY prompt it can't answer.
apps/sim/package.json Adds disposable-email-domains@1.0.62 as a direct dependency.

Sequence Diagram

sequenceDiagram
    participant Client
    participant emailHarmony as emailHarmony plugin
    participant Mailchecker as validateEmailWithMailchecker
    participant Disposable as isDisposableEmailDomain
    participant Cache as Module-level cache

    Client->>emailHarmony: POST /sign-up/email
    emailHarmony->>Mailchecker: validateEmailWithMailchecker(email)
    alt format invalid OR Mailchecker blocklist
        Mailchecker-->>emailHarmony: false
        emailHarmony-->>Client: 400 Invalid email
    else passes Mailchecker
        Mailchecker-->>emailHarmony: true
        emailHarmony->>Disposable: isDisposableEmailDomain(email)
        Disposable->>Cache: loadDisposableData()
        alt cache cold
            Cache->>Cache: dynamic import() exact + wildcard
            Cache-->>Disposable: Set + wildcards
        else cache warm
            Cache-->>Disposable: cached (immediate)
        end
        alt domain flagged as disposable
            Disposable-->>emailHarmony: true
            emailHarmony-->>Client: 400 Invalid email
        else domain clean
            Disposable-->>emailHarmony: false
            emailHarmony-->>Client: proceed with signup
        end
    end
Loading

Reviews (3): Last reviewed commit: "improvement(auth): defer disposable-doma..." | Re-trigger Greptile

Comment thread apps/sim/lib/messaging/email/disposable-domains.server.test.ts Outdated
@vercel

vercel Bot commented Jun 12, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Jun 12, 2026 10:37pm

Request Review

@greptile-apps

greptile-apps Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR layers disposable-email-domains (exact + wildcard lists) into the signup email validation path as a second blocklist alongside better-auth-harmony's bundled Mailchecker, keeping the heavy dataset server-side only and behind the existing SIGNUP_EMAIL_VALIDATION_ENABLED flag.

  • disposable-domains.server.ts: New server-only module builds an O(1) exact-domain Set and walks a small wildcard list for subdomain matching; imported in auth.ts and composed into emailHarmony's validator option so a signup is rejected if either list flags the domain.
  • auth.ts: emailHarmony() is now configured with a custom validator that unions Mailchecker and the new disposable-domain check — correctly preserving the original behaviour while adding the extra layer.
  • migrations.yml: Unrelated but useful improvement — dev db:push now captures stdout/stderr before checking exit status, turning an opaque TTY-prompt crash into an actionable GitHub Actions ::error annotation.

Confidence Score: 4/5

Safe to merge — the new blocklist is additive and fail-closed, so the worst realistic outcome of any dataset gap is a missed disposable address, not a broken signup flow for legitimate users.

The core validator composition in auth.ts is correct, the dataset is properly kept server-side, and the test suite covers the main paths well. The two minor gaps — no import 'server-only' enforcement and no test for the wildcard base-domain equality branch — don't affect correctness today but are worth addressing before the module grows more consumers.

apps/sim/lib/messaging/email/disposable-domains.server.ts and its test file — the server-only guard and the untested wildcard equality branch are both in this new module.

Important Files Changed

Filename Overview
apps/sim/lib/messaging/email/disposable-domains.server.ts New server-only helper that checks both an exact-domain Set and a wildcard linear scan; logic is correct but lacks import 'server-only' enforcement.
apps/sim/lib/auth/auth.ts Composes the new disposable-domain check into emailHarmony's validator; union logic (reject if either list flags) is correct, and short-circuit evaluation keeps malformed inputs safe.
apps/sim/lib/messaging/email/disposable-domains.server.test.ts Good coverage of exact, subdomain-wildcard, case-insensitivity, allow, and malformed cases; the wildcard base-domain-equality branch is the one uncovered path.
apps/sim/lib/messaging/email/disposable-domains.d.ts Correct ambient type declarations for the two un-typed JSON exports from disposable-email-domains.
.github/workflows/migrations.yml Dev-env db:push now captures output before testing the exit code; the &&/

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Signup request\nemail address] --> B{SIGNUP_EMAIL_VALIDATION_ENABLED?}
    B -- No --> Z[Allow signup]
    B -- Yes --> C[emailHarmony custom validator]
    C --> D{validateEmailWithMailchecker\nbetter-auth-harmony/email}
    D -- false\ndisposable per Mailchecker --> R[Reject signup]
    D -- true --> E{isDisposableEmailDomain\ndisposable-email-domains pkg}
    E -- exactDomains.has domain --> R
    E -- wildcardBaseDomains match --> R
    E -- no match --> Z
Loading

Reviews (2): Last reviewed commit: "improvement(auth): layer disposable-emai..." | Re-trigger Greptile

Comment thread apps/sim/lib/messaging/email/disposable-domains.server.ts Outdated
Comment thread apps/sim/lib/messaging/email/disposable-domains.server.test.ts Outdated
…ic import

Address review: load the ~120K-entry dataset on first use instead of at module import, so deployments with SIGNUP_EMAIL_VALIDATION_ENABLED off never pay the cost. Add a bare wildcard-base test case.
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

@TheodoreSpeaks TheodoreSpeaks merged commit 2c0a10a into staging Jun 12, 2026
15 checks passed
@TheodoreSpeaks TheodoreSpeaks deleted the investigate/botters branch June 12, 2026 22:44
TheodoreSpeaks added a commit that referenced this pull request Jun 13, 2026
TheodoreSpeaks added a commit that referenced this pull request Jun 13, 2026
#5018)

* Revert "improvement(auth): layer disposable-email-domains into signup email validation (#5010)"

This reverts commit 2c0a10a.

* feat(mailer): gate outbound email on AppConfig access-control ban list

* ci(migrations): restore dev schema-push TTY rename/drop guard dropped by #5010 revert
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