---
title: "Pre-Launch Security Checks"
slug: "pre-launch-security"
category: "ops"
tags: ["checklist", "security", "launch", "ops", "auth", "rls", "rate-limiting"]
status: "stable"
last_updated: 2026-09-27
summary: "Six security checks to verify before a new app takes real traffic: no secrets in the bundle, RLS on every table, rate-limited auth, guarded routes, server-side validation, and authenticated email."
related: ["[[ops/pre-launch-checklist]]", "[[ops/secrets-and-env]]", "[[backend/supabase-rls]]", "[[backend/auth-sessions]]", "[[backend/email-deliverability]]", "[[security/owasp-top-10]]", "[[howto/launch-a-new-site]]"]
---

## 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 [[ops/pre-launch-checklist]]; work through it before the breakage, legal, and observability items there are signed off. For the broader threat model, see [[security/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 [[ops/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 [[backend/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 [[backend/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 [[backend/email-deliverability]].

## Copy-paste checklist

Paste this block under the security heading of your pre-launch list.

```markdown
### 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)
```

## Related

- [[ops/pre-launch-checklist]]
- [[ops/secrets-and-env]]
- [[backend/supabase-rls]]
- [[backend/auth-sessions]]
- [[backend/email-deliverability]]
- [[security/owasp-top-10]]
- [[howto/launch-a-new-site]]
