If you vibe-coded an app with Claude or Cursor and you're about to hand it to a paying client, stop. AI-generated code ships with predictable security holes — exposed API keys, missing input validation, default database permissions that let any user see everyone else's data. These aren't edge cases. They happen in almost every first-pass AI-generated codebase.
This guide is the pre-launch security checklist. Follow it step by step before any real user touches your app. It's written for the most common vibe coding stack (Next.js + Supabase + Vercel), but the principles apply regardless of your tools.
Why AI-Generated Code Has Security Problems
AI models optimize for "does it work?" not "is it secure?" When you tell Claude "build me a task manager with user accounts," it will generate code that creates users, stores tasks, and displays them. What it probably won't do automatically: ensure User A can't see User B's tasks, validate that input fields can't accept malicious scripts, hide your API keys from the browser's developer tools, or add rate limiting to prevent someone from hammering your endpoints.
These aren't failures of the AI — they're gaps in the prompt. The AI builds what you asked for. You probably didn't ask for security because you were focused on features. Now it's time to go back and add it.
Step 1: Audit Your Environment Variables
This is the most common and most dangerous mistake in vibe-coded apps. Check every file in your project for hardcoded API keys, database URLs, or secrets.
What to look for: Search your codebase for strings that start with sk-, eyJ, sbp_, supabase, postgres://, or any long random-looking strings. Check these files specifically: any file in your /app or /pages directory, any component file, your next.config.js, and any utility files.
The fix: Move every secret into environment variables. In Next.js, only variables prefixed with NEXT_PUBLIC_ are exposed to the browser. Your database URL, service role key, and API secrets should never have this prefix.
# .env.local (NEVER commit this file)
SUPABASE_SERVICE_ROLE_KEY=your-secret-key
DATABASE_URL=postgres://...
# These are okay to expose to the browser:
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
Verify: Check your .gitignore file includes .env.local. If you've already committed secrets to Git, they're in your history even after deletion — rotate (regenerate) every exposed key immediately.
Step 2: Enable Row-Level Security in Supabase
This is the single most critical step if you're using Supabase. By default, Supabase tables have no access restrictions — anyone with your anon key can read and write every row in every table. This means User A can see User B's data with a simple API call.
The fix: Enable Row-Level Security (RLS) on every table, then create policies that restrict access.
Go to your Supabase dashboard → Table Editor → select each table → click "RLS Disabled" to enable it. Then add policies:
For a typical app where users should only see their own data, create a SELECT policy: auth.uid() = user_id. Create similar policies for INSERT, UPDATE, and DELETE.
Test it: Log in as User A, try to access User B's data through the API. If you can see it, your policies are wrong. Supabase has a SQL editor where you can test policies directly.
Common AI mistake: Claude often generates Supabase queries using the service_role key (which bypasses RLS) instead of the anon key with proper RLS policies. Check that your client-side code uses the anon key only. The service role key should only exist in server-side code (API routes, server actions) and never be exposed to the browser.
Step 3: Add Authentication Properly
AI-generated auth code often works but takes shortcuts. Check these specific issues:
Session management: Make sure sessions expire. Check that your auth setup includes a reasonable session timeout (Supabase defaults are generally fine, but verify). Ensure that logging out actually invalidates the session, not just clears the local cookie.
Password requirements: If you have email/password auth, enforce minimum password length (8+ characters). Supabase handles this in your project settings → Authentication → Password Requirements.
Protected routes: Every page that shows user-specific data needs authentication middleware. In Next.js App Router, create a middleware that checks for a valid session and redirects unauthenticated users to the login page. Don't rely on client-side checks alone — a user can bypass those by hitting your API directly.
Email verification: Enable email confirmation in Supabase Auth settings. This prevents people from creating accounts with fake email addresses and adds a basic layer of account validity.
Getting value from this? We publish one deep-dive per week on AI tools, workflows, and practical guides. Join the readers who get it first →
Step 4: Validate All Inputs
AI-generated forms usually have basic validation (required fields, email format) but rarely protect against malicious input.
What to add:
Server-side validation on every API endpoint. Never trust client-side validation alone — it can be bypassed by sending requests directly to your API. Use a validation library like Zod (for TypeScript) to define schemas for every piece of data your app accepts.
Sanitize HTML in any user-generated content that gets displayed. If your app has comments, descriptions, or any text field that renders in the browser, use a library like DOMPurify to strip dangerous scripts. Without this, someone can inject JavaScript that steals other users' sessions (cross-site scripting / XSS).
Limit file upload size and types if your app accepts file uploads. AI-generated upload handlers often have no limits, meaning someone could upload a 2GB file or an executable. Add size limits (5MB is reasonable for most apps) and restrict file types to what you actually need (images, PDFs, etc.).
Step 5: Secure Your API Routes
Check every API route or server action in your app for these issues:
Authentication on every endpoint. Every API route that returns or modifies user data should verify the user's session first. AI often generates API routes that accept requests from anyone.
Authorization beyond authentication. Even after confirming a user is logged in, verify they're allowed to access the specific resource they're requesting. "User A is logged in" doesn't mean "User A can edit User B's profile." Check ownership on every data access.
Rate limiting. Without rate limiting, someone can send thousands of requests per second to your API, either to scrape data or to overwhelm your server. Add basic rate limiting using a library like rate-limiter-flexible or use Vercel's built-in rate limiting on Edge Functions.
HTTP methods. Make sure your API routes only respond to the HTTP methods they should. A route that handles POST requests shouldn't also respond to DELETE requests unless you explicitly designed it to.
Step 6: Check Your Deployment Configuration
Your deployment platform has security settings that AI doesn't configure for you.
Vercel settings to check: Enable "Deployment Protection" (requires auth to view preview deployments — prevents clients from accidentally sharing preview URLs that expose in-progress work). Set up spending limits to prevent unexpected charges if your app gets traffic spikes. Configure your allowed domains in CORS headers.
Custom domain and SSL: If you're delivering this to a client, set up their custom domain with HTTPS. Vercel and Netlify handle SSL automatically. Never deliver a client app on a .vercel.app subdomain — it looks unprofessional and the client can't transfer it easily.
Headers: Add security headers to your next.config.js or vercel.json: X-Content-Type-Options: nosniff, X-Frame-Options: DENY (prevents your site from being embedded in iframes for clickjacking), Strict-Transport-Security (forces HTTPS). These are one-time additions that prevent entire classes of attacks.
Step 7: Run a Final Security Scan
Before handing anything to a client, run these free checks:
npm audit: Run npm audit in your project directory. It flags known vulnerabilities in your dependencies. Fix critical and high-severity issues. Run npm audit fix for automatic fixes where available.
Lighthouse: Open your deployed site in Chrome, open DevTools, run a Lighthouse audit. Check the "Best Practices" score — it catches common security issues like missing HTTPS, vulnerable libraries, and insecure headers.
Manual testing: Log in as one user, try to access another user's data by modifying URLs or API calls. Try submitting empty forms, oversized inputs, and special characters (like encoded XSS payloads). If any of these work, you have issues to fix.
The Pre-Launch Checklist
Print this and check every item before going live:
- All secrets in environment variables (no hardcoded keys)
.env.localin.gitignore(and no secrets in Git history)- Supabase RLS enabled on every table
- RLS policies tested (User A can't see User B's data)
- Client-side code uses anon key only (service role key server-side only)
- Authentication on every API route
- Authorization checks (ownership verification) on data access
- Input validation on every form (server-side, not just client-side)
- File upload limits (size and type) if applicable
- Rate limiting on API endpoints
- Security headers configured
npm auditrun and critical issues fixed- Custom domain with SSL configured
- Preview deployments protected
- Manual cross-user data access test passed
The Bottom Line
Securing a vibe-coded app isn't about becoming a security expert. It's about running through a checklist that catches the predictable gaps AI leaves behind. The steps above take 2–4 hours and prevent the most common vulnerabilities. For a client-facing app, this isn't optional — it's the difference between professional work and a liability.
If you're building your first vibe-coded app, start with our complete guide to vibe coding. If you want to improve the prompts you use with Claude or Cursor, try our free prompt optimizer. And for a breakdown of the best coding tools, see Claude Code vs Codex.
This is what we do every week. One deep-dive on AI tools, workflows, and honest takes — no hype, no filler. Join us →
Disclosure: Some links in this article are affiliate links. We only recommend tools we've personally tested and use regularly. See our full disclosure policy.