Pre-Launch Security Checklist
Pre-Launch Security Checklist
The 8 Checks Before You Go Live
Run through this list before your first real user hits your app. Each item here represents a class of attack that regularly takes down real production systems.
---
1. Environment Variables — No Secrets in Code
# Search your entire codebase for hardcoded secrets
grep -r "sk-" src/ # OpenAI key pattern
grep -r "eyJhb" src/ # JWT/Supabase key pattern
grep -r "ghp_" src/ # GitHub token pattern
grep -r "AKIA" src/ # AWS key patternNever hardcode API keys. One public GitHub repo with a leaked key can be exploited within minutes — automated scanners watch GitHub 24/7.
---
2. Supabase RLS — Every Table Locked Down
-- Check which tables have RLS disabled
SELECT tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY tablename;Every row in a Supabase table is publicly readable via the anon key unless RLS is enabled with policies. Run this query in the Supabase SQL editor before launch.
---
3. Rate Limiting on Auth Endpoints
// Supabase handles login rate limiting built-in
// For your own API endpoints, use upstash/ratelimit:
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '10 s'), // 10 requests per 10s
})
export async function POST(req: Request) {
const ip = req.headers.get('x-forwarded-for') ?? 'anonymous'
const { success } = await ratelimit.limit(ip)
if (!success) {
return new Response('Too Many Requests', { status: 429 })
}
// ... your handler
}---
4. Security Headers in next.config.js
const nextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload',
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()',
},
],
},
]
},
}Check your headers at [securityheaders.com](https://securityheaders.com).
---
5. Input Validation with Zod
Never trust user input. Validate everything at the API boundary:
import { z } from 'zod'
const CreateCourseSchema = z.object({
title: z.string().min(1).max(200).trim(),
description: z.string().max(2000).optional(),
published: z.boolean().default(false),
})
export async function POST(req: Request) {
const body = await req.json()
const result = CreateCourseSchema.safeParse(body)
if (!result.success) {
return Response.json({ error: result.error.flatten() }, { status: 400 })
}
const { title, description, published } = result.data
// title is now guaranteed to be a non-empty string under 200 chars
// SQL injection via title is impossible with parameterized queries
}---
6. CORS — Only Allow Your Domains
Already covered in Module 4. Verify your production CORS config only allows your actual frontend domain, not *.
---
7. Dependency Audit
npm audit # shows known vulnerabilities
npm audit fix # auto-fixes non-breaking updates
npm audit fix --force # upgrades major versions (test carefully)Run npm audit before every major deployment. Known vulnerabilities in dependencies are one of the most common attack vectors.
---
8. .gitignore Verification
# Verify these are ignored
cat .gitignore | grep -E "\.env|\.key|\.pem"
# Check nothing sensitive is tracked
git ls-files | grep -E "\.env|secret|key|credential"If a secret was ever committed (even in old commits), rotate it immediately — git history is public and permanent.
---
Quick Security Score
| Check | Tool |
|---|---|
| Security headers | securityheaders.com |
| SSL configuration | ssllabs.com/ssltest |
| Known vulnerabilities | npm audit |
| RLS coverage | Supabase SQL editor |
| Exposed secrets | GitHub secret scanning (automatic on public repos) |