The Vibe-Coding Pre-Launch Security Playbook
AI code assistants let you ship at lightspeed, but they routinely forget database authorization, leak API credentials in client builds, and omit security headers. Complete this interactive checklist before pointing your custom domain.
Enable Row Level Security (RLS) on ALL Public Tables
When Cursor or Lovable generates a Supabase table, RLS is often disabled by default. Anyone with your public Anon Key can query `POST /rest/v1/<table_name>` and dump all customer records.
-- 1. Enable RLS ALTER TABLE public.user_profiles ENABLE ROW LEVEL SECURITY; -- 2. Restrict Read to Owner CREATE POLICY "Users can only read their own profile" ON public.user_profiles FOR SELECT USING (auth.uid() = id); -- 3. Restrict Updates to Owner CREATE POLICY "Users can only update their own profile" ON public.user_profiles FOR UPDATE USING (auth.uid() = id);
Never Prefix Secret Keys with NEXT_PUBLIC_ or VITE_
Variables prefixed with `NEXT_PUBLIC_` or `VITE_` are baked directly into compiled client-side JavaScript bundles, making them visible to anyone inspecting DevTools.
// ❌ WRONG (Leaked to client bundle): // NEXT_PUBLIC_STRIPE_SECRET_KEY=sk_live_... // ✅ CORRECT: STRIPE_SECRET_KEY=sk_live_... // Use ONLY in Server Components, Server Actions, or Route Handlers: import Stripe from 'stripe'; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
Disable Production Source Maps (.js.map)
Source maps allow reverse-engineers to reconstruct your entire TypeScript codebase, database schemas, internal endpoint structures, and hidden business logic.
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
// Ensure production sourcemaps are never uploaded to public CDN
productionBrowserSourceMaps: false,
};
export default nextConfig;Enforce Session Authentication Inside Server Actions
Next.js Server Actions are public POST endpoints. Never trust client arguments like `userId`. Always verify the caller’s session on the server side.
'use server';
import { auth } from '@/auth'; // Or supabase.auth.getUser()
export async function updateUserProfile(formData: FormData) {
// 1. Authenticate session on server
const session = await auth();
if (!session?.user?.id) {
throw new Error('Unauthorized');
}
// 2. Perform mutation strictly for authenticated user
const newName = formData.get('name') as string;
await db.update(users).set({ name: newName }).where(eq(users.id, session.user.id));
}Configure Strict OWASP Defense Headers
Prevent clickjacking (iframe embedding), MIME sniffing, and enforce HTTPS with HSTS.
headers: async () => [
{
source: '/:path*',
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' },
],
},
]Deploy /llms.txt for ChatGPT, Perplexity & Claude Discovery
Vibe-coded SPAs often render as blank shells to AI search bots. Publish `/llms.txt` in your public folder so LLMs can cite your SaaS.
# Your Product Name > Clean 1-sentence value proposition for LLM models. ## Core Features - Feature A: Detail - Feature B: Detail ## Documentation & API - https://yourdomain.com/docs
Automate This Entire Checklist in 30 Seconds
Scorpr tests these 6 rules and dozens more automatically via live external headless probes.