← All posts
·6 min read

Security Checklist Before Launching Your AI-Generated SaaS

Twenty-five security checks to complete before your AI-built SaaS goes live. Covers authentication, secrets, headers, database access, and payment security.

You're close to launching. The app works. The landing page is live. You've told people it's coming.

Before you flip the switch, go through this checklist. These are the issues that show up in post-launch security audits — and a few of them are the kind that make headlines if they're not caught first.

This list is organized by area. Work through it top to bottom. Check each item off when it's done.


Authentication and authorization

  • [ ] Every API route that returns user data has an authentication check. Look at every file in your /api or /app/api directory. Each one that touches user data should have a session verification step before it does anything else.

  • [ ] Every API route that modifies data verifies the user is allowed to make that change. Authentication (is this a logged-in user?) is different from authorization (is this the right user for this action?). An authenticated user should not be able to modify another user's data.

  • [ ] Admin routes are behind an admin-only check. If you have a /admin section, every route it calls should verify the user has admin privileges server-side — not just on the client.

  • [ ] Password reset and magic link flows expire tokens. One-time tokens for password reset, email verification, and magic links should have short expiry times (15–30 minutes) and be invalidated after use.

  • [ ] Session tokens are stored in httpOnly cookies, not localStorage. localStorage is accessible to any JavaScript on your page, including third-party scripts. httpOnly cookies are not.


Secrets and environment variables

  • [ ] No API keys or secrets are in your source code or git history. Search your repository for common patterns: sk-, sk_live_, SG., AKIA, AIza. Also run git log -p | grep -E "sk-|secret|password" to check the history.

  • [ ] Every secret is in an environment variable on your hosting platform. In Vercel: Settings → Environment Variables. In Railway: Variables. Never hardcode values that vary between environments.

  • [ ] No environment variable with a secret is prefixed with NEXT_PUBLIC_. NEXT_PUBLIC_ variables are bundled into the client JavaScript. Database URLs, Stripe secret keys, and any key with "secret" in the name must not use this prefix.

  • [ ] Your Stripe publishable key is the only Stripe key in the frontend. The publishable key (pk_live_...) is safe for the browser. The secret key (sk_live_...) must never reach the client.

  • [ ] External API calls that need secrets go through a server-side route, not directly from the browser. If your app calls OpenAI, Anthropic, or any other API that requires a secret key, the call must happen server-side.


Database security

  • [ ] Row Level Security is enabled on every Supabase table. Log into Supabase → Table Editor. Every table should show a locked shield icon. If any shows red, enable RLS and add appropriate policies.

  • [ ] RLS policies are in place for every table with user data. Enabling RLS without policies blocks all access. Add at minimum a SELECT policy that filters by the authenticated user's ID.

  • [ ] The Supabase service role key is not in any client-side code. The service role key bypasses RLS entirely. It must only be used server-side in trusted environments.

  • [ ] Direct database URLs are not in any client-side code. PostgreSQL connection strings allow direct database access, bypassing all application-level security.

  • [ ] User-supplied data is validated and sanitized before it reaches the database. Never pass raw user input directly to a database query. Use parameterized queries or ORM methods that handle this automatically.


Network and transport security

  • [ ] Your app is served over HTTPS. If you're using Vercel, Netlify, Railway, or similar, this is automatic. If you're on a custom server or VPS, verify TLS is configured correctly.

  • [ ] CORS is configured to allow only your specific domain. Access-Control-Allow-Origin: * is a development convenience, not a production setting. Set it to your exact domain.

  • [ ] Security headers are in place. At minimum: X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Strict-Transport-Security: max-age=31536000, Referrer-Policy: strict-origin-when-cross-origin. Add these in vercel.json or next.config.js.

  • [ ] Mixed content is not present. An HTTPS page loading resources over HTTP will show browser warnings and can expose your users. Check DevTools Console for mixed content warnings.


Payment security

  • [ ] Stripe secret key is only ever used server-side. Create a /api/create-checkout or /api/create-payment-intent route. Your frontend calls your route; your route calls Stripe. Never the other way.

  • [ ] Payment amounts are calculated server-side, not passed from the frontend. If your frontend sends a price to your API, an attacker can modify it. Prices must be looked up from your database or configuration on the server.

  • [ ] Stripe webhooks are verified with the webhook signing secret. Unverified webhooks can be spoofed. Use stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET) before trusting any webhook event.


Final checks

  • [ ] Run VibeScan on your deployed app before launch. VibeScan checks your live app for missing headers, exposed secrets, CORS issues, and more. It takes under 60 seconds and catches the things that are hardest to verify manually.

  • [ ] Your robots.txt does not list internal or admin routes. Listing a path in robots.txt to block crawlers also tells anyone who reads the file that the path exists. Use authentication to protect sensitive routes, not obscurity.

  • [ ] You have a plan for what to do if you find an exposed key. Know how to revoke and rotate each API key you use. Bookmark the key management pages for Stripe, OpenAI, and your other services. If you discover an exposure post-launch, speed matters.


After you launch

Security is not a one-time task. Each time you add a feature — especially one that touches payments, user data, or third-party integrations — re-run the relevant sections of this checklist.

Set a reminder to run a VibeScan scan once a month. The scan is free and takes under a minute. You'll catch configuration drift, newly added secrets that ended up in the wrong place, and CORS issues introduced by updates.

The apps that get compromised are almost never targeted by sophisticated attackers. They're caught by automated scanners looking for the predictable vulnerabilities that show up in AI-generated code. The checklist above covers all of them.