Overview
These are the security gaps most likely to be open on a new deployment, because each one is easy to leave in a development shortcut and invisible until someone probes it. Verify each check against the production build, not a local dev server. This page is the security section of pre-launch-checklist; work through it before the breakage, legal, and observability items there are signed off. For the broader threat model, see owasp-top-10.
Keep secrets out of the client bundle
Search your built output (dist/ or .next/) for strings that match your secret key patterns. Private keys, service-role tokens, and database URLs must never appear in any file served to the browser. See secrets-and-env for environment variable discipline.
Enable RLS on every table
Supabase (and Postgres in general) disables row-level security by default on tables created via raw SQL. Run SELECT tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public'; and confirm rowsecurity = true for every table. A table with RLS disabled and a public Data API key exposes all rows to any request. See supabase-rls for policy patterns.
Rate-limit public and auth endpoints
Auth endpoints (login, signup, password reset) must be rate-limited before launch; automated login attempts can begin soon after a new hostname appears, since bots watch public Certificate Transparency logs for fresh certificates. One reasonable starting point, to tune against real traffic: 5 login attempts per 15 minutes per IP and 3 password-reset requests per hour. General public API endpoints should have a ceiling even if generous. Cloudflare WAF rules or middleware-level limiting both work; pick one and verify it fires.
Enforce auth on every protected route and API handler
Write a test that calls each protected endpoint without a session token and confirm it returns 401, not data. It is common during development to comment out auth guards and forget to restore them. See auth-sessions for session validation patterns.
Validate input at the server boundary
Validate and sanitize all user input on the server, not only in the client form. Client-side validation is a UX courtesy; server-side validation is the security control.
Authenticate transactional email
If the app sends password resets, verification links, or receipts, confirm SPF, DKIM, and DMARC are set on the sending domain before launch so the one email that matters does not land in spam. See email-deliverability.
Copy-paste checklist
Paste this block under the security heading of your pre-launch list.
### Security
- [ ] Built output searched for secret key patterns; none found
- [ ] RLS enabled on every public-schema table (verified via pg_tables)
- [ ] Auth endpoints rate-limited (starting point: login 5/15 min, reset 3/hr per IP)
- [ ] General public API endpoints have a ceiling rate limit configured
- [ ] Every protected route returns 401 without a valid session token
- [ ] Server-side input validation present on all form and API handlers
- [ ] SPF, DKIM, and DMARC set on the sending domain (transactional email lands in inbox)