AI-generated code works. It runs. It looks right. It also ships with the same five security vulnerabilities in almost every project. These aren't theoretical risks — they're the specific holes that show up in nearly every codebase produced by Claude, Cursor, Replit, or Copilot when you don't explicitly ask for security.

I've audited dozens of vibe-coded projects over the past year, and the same five mistakes appear in at least 80% of them. Each one takes under 30 minutes to fix. Here's what to look for and exactly how to fix it.

Quick Facts
Who this is for
Anyone shipping AI-generated code to users or clients
Time to fix all 5
1–3 hours depending on complexity
Stack assumed
Next.js/React + Supabase/Firebase + Vercel/Netlify
Severity
Mistakes #1 and #2 can expose user data — fix those first
Last verified
April 2026

Mistake 1: API Keys Hardcoded in Frontend Code

How it happens: You tell Claude "connect to Supabase" or "add Stripe payments," and it generates code with the API key pasted directly into a React component. The code works perfectly — and your secret key is visible to anyone who opens browser DevTools and checks the Network tab or the JavaScript source.

Why it's dangerous: Your Supabase service role key bypasses all database security. Your Stripe secret key lets someone charge cards or issue refunds. Your OpenAI key lets someone rack up thousands in API calls on your account. Bots actively scan public GitHub repos and deployed sites for exposed keys.

The fix: Move every secret to environment variables. In Next.js, only variables starting with NEXT_PUBLIC_ reach the browser. Any key that grants write access, admin access, or financial access should be server-side only.

After moving keys to .env.local, search your entire codebase for the old key values to make sure they're gone. Then check your Git history — if the key was ever committed, it's still in your repo history even if you deleted it. Regenerate (rotate) any key that was ever exposed, even briefly.

Time to fix: 15–30 minutes.

Mistake 2: Supabase Tables Without Row-Level Security

How it happens: AI generates Supabase table creation queries and CRUD operations, but doesn't enable Row-Level Security (RLS). By default, Supabase tables with RLS disabled are fully accessible to anyone with your project URL and anon key — both of which are public (and should be).

Why it's dangerous: Without RLS, User A can query User B's data. A simple fetch call from the browser console can pull every row from every table. Your entire database is effectively public.

How to spot it: Go to your Supabase dashboard → Table Editor. If any table shows "RLS Disabled," you have this problem. Also check if your client-side code uses the service_role key instead of the anon key — that's even worse.

The fix: Enable RLS on every table. Then create policies that match your app's access patterns. The most common policy: users can only SELECT, INSERT, UPDATE, and DELETE rows where auth.uid() = user_id. Test by logging in as one user and attempting to access another user's data through the Supabase REST API.

Time to fix: 30–60 minutes depending on table count.

Getting value from this? We publish one deep-dive per week on AI tools, workflows, and practical security guides. Join the readers who get it first →

Mistake 3: No Input Validation on Forms or API Routes

How it happens: AI generates forms that accept any input and API routes that trust whatever data comes in. The form has a "required" attribute in HTML, but no server-side validation. Someone bypasses the form entirely by sending a direct API request with whatever payload they want.

Why it's dangerous: Without server-side validation, attackers can inject scripts into your database (stored XSS), send oversized payloads that crash your server, or submit data that breaks your app's logic (like a negative price or an email field containing SQL).

How to spot it: Open any API route or server action in your codebase. If it uses req.body or form data without checking the shape, type, and length of every field, you have this problem.

The fix: Add server-side validation to every endpoint using a schema validation library. Zod is the standard for TypeScript projects:

TypeScript
import { z } from 'zod';

const taskSchema = z.object({
  title: z.string().min(1).max(200),
  description: z.string().max(2000).optional(),
  priority: z.enum(['low', 'medium', 'high']),
});

// In your API route:
const parsed = taskSchema.safeParse(req.body);
if (!parsed.success) {
  return Response.json({ error: parsed.error }, { status: 400 });
}

For any field that renders as HTML (comments, descriptions, bios), also sanitize the output with DOMPurify before displaying it.

Time to fix: 30–60 minutes.

Mistake 4: No Rate Limiting on API Endpoints

How it happens: AI never adds rate limiting unless you ask for it. Every API route your app exposes can be hit thousands of times per second by anyone with a script.

Why it's dangerous: Without rate limiting, someone can brute-force your login endpoint, scrape your database by hitting list endpoints repeatedly, overwhelm your server (denial of service), or burn through your API quota if your endpoints call external services like OpenAI or Stripe.

How to spot it: If none of your API routes check how many requests a single IP or user has made recently, you have no rate limiting.

The fix: The simplest approach for Vercel-hosted apps is an in-memory rate limiter for development and a Redis-based one for production. Vercel's Edge Middleware can handle basic rate limiting. Upstash Redis (free tier) with @upstash/ratelimit gives you production-grade rate limiting in a small amount of code.

A reasonable starting point: 60 requests per minute for authenticated users, 20 per minute for unauthenticated users, and 5 per minute for login/signup endpoints (to prevent brute forcing).

Time to fix: 20–45 minutes.

Mistake 5: Missing Auth Checks on Protected Pages and APIs

How it happens: AI generates a beautiful dashboard page and API routes that return user data, but doesn't add middleware to verify the user is actually logged in. The page "works" because during development you're always logged in. In production, someone can access the dashboard URL directly without logging in, or hit the API endpoint and get data back.

Why it's dangerous: Unauthenticated access to protected pages means anyone can see user dashboards, admin panels, or private data just by guessing the URL. Unprotected API routes mean any tool like Postman or curl can pull data without credentials.

How to spot it: Open an incognito browser window (not logged in) and navigate directly to your dashboard, settings, or admin URLs. If you can see any content without being redirected to a login page, you have this problem. Then try hitting your API routes directly — if they return data without a valid session cookie or auth token, those are unprotected too.

The fix: Add authentication middleware that runs before every protected route. In Next.js App Router, create a middleware.ts at your project root:

TypeScript
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const session = request.cookies.get('session');
  if (!session) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
  return NextResponse.next();
}

export const config = {
  matcher: ['/dashboard/:path*', '/settings/:path*', '/api/protected/:path*'],
};

Adapt the matcher to cover every route that should require authentication. Test in incognito after implementing.

Time to fix: 15–30 minutes.

How to Prevent These in Future Projects

The pattern is clear: AI generates code that works functionally but skips security because you didn't ask for it. The fix is to ask for it upfront.

Add this to the end of your initial prompt for any vibe-coded project:

Prompt snippet
SECURITY REQUIREMENTS:
- All secrets in environment variables (never hardcoded)
- Supabase RLS enabled on all tables with per-user policies
- Server-side input validation on every API route (use Zod)
- Rate limiting on all public endpoints
- Authentication middleware on all protected routes
- No service role key in client-side code

This won't catch everything, but it eliminates the five most common vulnerabilities on the first pass instead of requiring a cleanup later.

If you want to get better at writing prompts that produce more secure code from the start, our free prompt optimizer can help you structure your instructions. And for a complete security walkthrough, see our step-by-step guide: How to Secure a Vibe-Coded App Before Giving It to Clients.

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.