JSTAcademy
0 XP
Dashboard
Crash Courses
Pre-Launch Security Checklist
14 min
Masters+150 XP
Crash Courses · Masters

Pre-Launch Security Checklist

The security checks every developer must run before pointing real users at their app.
14 min read+150 XP on completionCert: Ship It Crash Course
Tap any word in the text below to start reading from there.

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 pattern

Never 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

CheckTool
Security headerssecurityheaders.com
SSL configurationssllabs.com/ssltest
Known vulnerabilitiesnpm audit
RLS coverageSupabase SQL editor
Exposed secretsGitHub secret scanning (automatic on public repos)
0%