A stranger can drive your email volume before you have a single real user
The concrete risk on launch day is not a stolen account. It is volume. Supabase Auth's email sign-in flows, magic link and email OTP, send a real email every time someone submits an address to the sign-in endpoint. The endpoint is public by design, because that is how people log in. If the rate limits are loose, anyone can hit it in a loop with a script and make your project send a burst of emails, each one to whatever address they typed.
Two things go wrong from there. First, your sending allowance gets consumed by traffic that was never going to convert, and on the default Supabase-managed email service that allowance is small, so legitimate users start seeing send failures. Second, the addresses an attacker submits are often not theirs, so strangers receive unrequested login codes from your domain, which is exactly the pattern mailbox providers read as spam and use to lower your sender reputation.
Be precise about what is observable versus what is only possible. From the outside, you can observe whether the auth endpoint returns rate-limit responses under repeated requests, and you can observe how fast it accepts repeated submissions. What you cannot observe from outside is the exact per-hour numbers configured in your dashboard, or whether a given send actually reached an inbox. This is an abuse and availability edge, not an authentication bypass: the attacker is not logging in as anyone. They are using a working public endpoint more than you intended.
Why AI-built Supabase apps land here
Supabase ships with auth rate limits already set, but the defaults are tuned for development, not for a public URL with no human watching it. The email send limit in particular is generous enough that a single beta tester never notices it, and a project built and shipped in an afternoon never gets the pass where someone tightens it.
The deeper reason is that the rate limit is a dashboard setting, not code. An AI builder generates the sign-in form, wires up supabase.auth.signInWithOtp, and the flow works on the first try. Nothing in that generated code surfaces the rate-limit knobs, because they live in the Supabase Auth configuration, not in the repo. So the limit is invisible during the build, the same way a missing table policy is invisible while the app works from the inside.
- The sign-in flow works in testing, so the loose limit never shows itself.
- The limit lives in the dashboard, not in generated code, so a code review of the repo will not catch it.
- Fast deploy means the public endpoint exists before anyone reviews the auth config, and a handful of beta testers exercise it far below any threshold.
- Default email sending on a Supabase-managed sender is throttled and meant for testing, which makes a burst hurt sooner than people expect. This is one of the AI-generated insecure defaults pattern; for the wider auth surface, see magic-link auth in AI-built apps.
Check the limits and the endpoint behaviour
You can confirm this on your own project without attacking anything. Use two angles, the dashboard config and the live endpoint.
From the Supabase dashboard: open Authentication, then the Rate Limits section. Supabase exposes per-hour limits there, including the email send limit that covers magic links and OTP, plus separate limits for token verification and other auth actions. Read the email send number against your real expected sign-in volume. A number left at the development default, far above what your launch traffic needs, is the suspicious result. Also confirm which email provider is configured: the built-in Supabase sender is for testing and is the most easily exhausted, so production traffic should run through your own SMTP or a configured provider.
From the public surface, against your own project only: submit your own address to your sign-in form a few times in quick succession and watch the Network tab in DevTools. The request goes to your project's auth endpoint, the /auth/v1/otp path on your *.supabase.co URL.
- Safer result: after a small number of attempts the endpoint returns an HTTP 429 with a rate-limit response, and the body names the limit. That means a ceiling exists and fires.
- Suspicious result: repeated submissions keep returning success with no 429 and no slowdown, so there is no visible ceiling between an outsider and your send volume.
- Stop as soon as you have your answer. You are checking for the presence of a limit on your own project, not stress-testing it, and every accepted request is a real email send against your own allowance.
Tighten the config, and know what it does not cover
The fix is configuration, not a code change, and it has two parts.
Set the auth rate limits to match real launch traffic. In Authentication, Rate Limits, lower the email send limit to a value a real user would never exceed but an abusive loop would, and review the verification and sign-up limits in the same pass. Lower is safer here: you can always raise a limit when genuine demand appears, but you cannot un-send a burst of emails or recover spent sender reputation. Then move email off the built-in testing sender onto your own SMTP or a provider, so your sending volume and reputation are yours to manage rather than a shared, throttled default.
Be honest about the boundary of this fix. A per-hour send limit reduces the blast radius of a flood, but it does not by itself stop a determined attacker cycling through many addresses, and it does not validate that a submitted address belongs to the person submitting it. The limit is one control. Pairing it with a CAPTCHA or attestation challenge on the sign-in and sign-up forms, which Supabase supports, raises the cost of automated abuse considerably; the rate-limit setting on its own does not add that challenge. For the related flooding pattern on open submission forms, see waitlist and signup form abuse.
Rate limiting is an availability and cost control, not an authentication or authorization control. It limits how often the endpoint runs; it does not decide who is allowed in, and it does not confirm an email address is genuine. Treat it as one layer alongside CAPTCHA, a managed sender, and your auth logic, not a substitute for them.
Where VibeCodeGuard fits, and what it does not prove
VibeCodeGuard's Launch Check works from the public surface, so on the auth path it probes your sign-in endpoint and reads the response for rate-limit headers, the 429 status and a Retry-After or limit header that indicate a ceiling is present and firing. A missing rate-limit signal where you expected one is something the scan can surface from outside, with severity and a pointer to the dashboard setting to review.
What it cannot do is read your dashboard. The exact per-hour numbers, whether you are on the built-in sender or your own SMTP, and whether the configured limit actually matches your traffic are settings only you can see, so confirming the configuration is correct is a manual review inside Supabase. The scan checks whether a limit appears to exist on the public endpoint, not whether the number behind it is the right one.
Next step: open Authentication, Rate Limits in your Supabase dashboard, set the email send limit to your real launch traffic, confirm you are on your own sender, then run a focused Launch Check against the public URL before sharing it, so a missing rate-limit signal shows up in your scan and not in your inbox the day you go live.
A rate-limit signal on the public endpoint does not prove your auth flow is safe to ship. It does not measure the configured limit, confirm a managed sender, check for a CAPTCHA challenge, or verify the rest of your auth logic. Treat a missing signal as something to fix before launch, and treat a present signal as one data point, not a clean bill of health.
> launch check
Scan the public surface before launch.
Get severity, evidence, and practical fix guidance for the checks VibeCodeGuard can run from the outside.