EngineeringEngineeringSecurityVibe coding

Vibe coding security checklist: 28 checks before you launch

fivetwentyoneNovember 7, 202613 min read
A developer in a black hoodie reviews code on a large monitor in a sunlit office

Most vibe-coded apps go live with secrets in the browser, endpoints anyone can call, and database tables anyone can read. The code works, the demo looks great, and nothing in the build process ever flags the problem.

That is the pattern we see when founders bring us an AI-generated app to finish or fix. Veracode's 2025 GenAI Code Security Report found that 45% of coding tasks given to more than 100 AI models produced code with security vulnerabilities, and the models picked an insecure approach over a secure one 45% of the time. The assistant is not careless. It is optimizing for "the error went away", and the fastest way to make an error go away is often to loosen a rule.

This checklist covers the 28 checks we run before any AI-assisted build goes live. It is written for whatever stack your assistant chose, whether that is Next.js, Nuxt, SvelteKit, Remix, Laravel, Django, Rails, or a Supabase or Firebase backend with a thin front end on top. The last section gives you a prompt that makes your assistant run the whole audit for you.

Why vibe-coded apps share the same holes

AI coding assistants fix the problem in front of them, and security problems are rarely in front of them. A failed build, a red error in the terminal, a feature that does not render yet. Those get attention. A database table that anyone on the internet can read produces no error at all, so it never gets fixed.

A few habits come up again and again in AI-generated codebases.

  • Private keys get a "public" prefix so the front end can read them, which also lets every visitor read them.
  • Authorization gets placed in one gatekeeper file at the edge of the app, while the actual data queries behind it trust anyone who reaches them.
  • Database access rules get set to "allow all" during development and never tightened.
  • Endpoints that call paid AI or email services get no rate limit, so a single script can run up your bill overnight.
  • Whole database records get passed to the front end when the screen only needs a name and a date.

None of these is exotic. Every one of them maps to a category in the OWASP Top 10, which has been the standard list of web application risks for two decades. The checks below are organized in the order an attacker would find them, starting from what is visible in the browser and working inward.

Checks 1 to 4. Secrets and environment variables

The first place to look is the code your visitors can download. Every modern framework has a convention for marking an environment variable as safe to ship to the browser. Next.js uses NEXT_PUBLIC_, Vite uses VITE_, Create React App used REACT_APP_, Expo uses EXPO_PUBLIC_, and Nuxt uses the public block of its runtime config. AI assistants reach for that prefix whenever a build complains that a variable is undefined on the client, and that is how a Stripe secret key ends up in a JavaScript file anyone can open.

Check 1. Audit every browser-exposed variable. Open your .env files and your hosting dashboard (Vercel, Netlify, Railway, Fly, Render, or whatever you deploy to). Only true public identifiers should carry the public prefix. Stripe publishable keys, analytics IDs, and Supabase anon keys are fine. Secret keys, service role keys, database URLs, JWT secrets, and anything with "private" in its name are not. The Vite documentation and the Next.js documentation both spell out the same rule in their own words.

Check 2. Search the built output for secrets. Run your production build, then search the output folder (dist, .next/static, build, .output/public) for strings like sk_live, service_role, DATABASE_URL, JWT_SECRET, password, and bearer. A plain text search takes a minute and catches most leaks that the prefix audit missed.

Check 3. Confirm secrets were never committed to Git. Your .gitignore should list .env*, and your Git history should contain no earlier commit where a secret slipped in before that rule existed. GitHub's secret scanning catches many common key formats on public repositories. If anything was ever committed, rotate the key, because removing it from history does not remove it from every clone that was ever pulled.

Check 4. Mark server-only files as server-only. Most frameworks offer a way to make the build fail if a server file is imported into client code. Next.js has the server-only package, SvelteKit refuses to bundle anything under $lib/server, and Nuxt keeps server/ out of the client bundle entirely. Put your database client, billing helpers, and admin utilities behind whatever your framework provides so a careless import becomes a build error instead of a leak.

Checks 5 to 9. Server endpoints and API routes

Every server function your front end calls is a public URL. This is true of Next.js server actions, Nuxt and SvelteKit server routes, Remix actions, Django views, Laravel controllers, Rails endpoints, and plain Express handlers. The button in your interface is one way to reach that URL. A curl command is another, and the second one skips every check you built into the button.

Check 5. Authenticate every server function and route handler. The session check belongs at the top of each function, before anything else runs. Do not rely on the fact that the button is hidden for logged-out users, or that the page redirects them. Those are front-end conveniences, and the endpoint has no idea whether they ran.

Check 6. Validate inputs with a schema. Parse every incoming payload and URL parameter against a schema (Zod or Valibot in TypeScript, Pydantic in Python, strong parameters in Rails, form requests in Laravel) before the data touches a database. Reject fields you did not expect. The OWASP input validation cheat sheet covers the reasoning if your assistant pushes back.

Check 7. Filter every query by the current user. This is the most common serious bug we find in AI-generated apps. The code fetches a record by its ID, and the ID is whatever the request supplied. Change the number in the URL and you are reading someone else's invoice. Every read, update, and delete should filter by both the resource ID and the authenticated user's ID. OWASP calls this broken access control and ranked it the number one web application risk.

Check 8. Verify webhook signatures. Stripe, Clerk, Svix, Paddle, Resend, and every other provider that calls your app signs the payload. If your webhook handler processes the body without checking the signature, anyone who learns the URL can mark their own order as paid. The Stripe webhook documentation shows the verification step; the pattern is the same elsewhere.

Check 9. Treat your edge gatekeeper as a convenience, not a wall. AI assistants love putting all authorization in one middleware or edge function. In March 2025 a single crafted header let attackers skip Next.js middleware entirely (CVE-2025-29927), which meant every app that relied on it alone was open. Keep the middleware for redirects and nice error pages, and put the real check next to the data access.

Checks 10 to 13. Authentication and sessions

Whether you use a hosted provider like Clerk, Auth0, or Supabase Auth, or your assistant rolled its own, the session mechanics need the same review.

Check 10. Lock down the session cookie. The cookie that proves a user is logged in should carry HttpOnly (JavaScript cannot read it), Secure (HTTPS only in production), and SameSite=Lax or Strict (other sites cannot send it). MDN's Set-Cookie reference explains each flag in plain language.

Check 11. Make logout mean something on the server. Clearing local state logs the browser out of the interface. It does not invalidate the token, so anyone who captured it can keep using it. Logout should revoke the session server-side and clear the cookie.

Check 12. Confirm password hashing if auth is custom. Passwords belong behind argon2, bcrypt, or scrypt. Plaintext, MD5, SHA-1, and plain SHA-256 are all wrong for this job, and AI assistants will cheerfully write all four. The OWASP password storage cheat sheet is the reference to hand your assistant.

Check 13. Update the framework past known auth bypasses. Run your package manager's outdated or audit command and read the changelog for anything touching middleware, sessions, or cookies. Framework security patches are frequent and rarely dramatic in the release notes.

Checks 14 to 17. Database access rules

Backend-as-a-service products let the browser talk to the database directly, which is what makes them fast to build on. It also means the database's own rules are the only thing standing between a visitor and every table. AI-generated projects on Supabase, Firebase, Convex, PocketBase, and similar platforms routinely ship with those rules set to allow everything.

Check 14. Turn on row-level access rules for every table. On Supabase and any Postgres setup queried from the browser, row-level security must be enabled on every table in the public schema, with no exceptions for "internal" tables. On Firebase the equivalent is security rules on every collection and storage bucket. Convex, PocketBase, and Appwrite each have their own rule layer and the same requirement.

Check 15. Read the rules, do not just confirm they exist. A policy of USING (true) on Postgres or allow read, write: if true on Firebase is the same as no rule. Each select, insert, update, and delete policy should compare the row's owner column to the signed-in user's ID or role claim.

Check 16. Keep admin credentials off the client. The Supabase service role key, the Firebase Admin SDK credentials, and any equivalent that bypasses the rules above belong in server code only. If one of them has a public prefix, it fails Check 1 as well.

Check 17. Use parameterized queries everywhere. ORMs like Prisma, Drizzle, SQLAlchemy, and ActiveRecord do this by default. The risk appears when an assistant writes a "quick" raw query by gluing strings together. Search the codebase for template literals or string concatenation inside anything that looks like SQL. OWASP's injection page describes what happens when that goes wrong.

Checks 18 and 19. Data sent to the browser

Server-rendered frameworks make it easy to fetch a record on the server and hand it to a component. If that record is the full user row, every field in it gets serialized into the page HTML, including the ones the screen never shows.

Check 18. Pick fields before passing data to the client. Before a server component or loader passes props to anything that runs in the browser, select only what the interface needs. Password hashes, internal flags, billing IDs, and other users' email addresses should never be in that object.

Check 19. Read the raw page source. Load your key pages, open View Source, and search for field names you would not want a visitor to see. In Next.js the payload sits inside __next_f blocks. In Nuxt it is __NUXT__. In SvelteKit and Remix it is in the data script tag. The field names are different, the habit is the same.

Checks 20 to 23. User input, redirects, and uploads

Veracode measured an 86% failure rate on cross-site scripting tasks, which means AI assistants get this category wrong far more often than they get it right.

Check 20. Sanitize any HTML you render from user content. Search for dangerouslySetInnerHTML, v-html, {@html}, |safe, {!! !!}, html_safe, and innerHTML. Anywhere user-supplied content reaches one of these, it should pass through a sanitizer like DOMPurify first.

Check 21. Validate redirect targets. Login and callback routes often accept a redirectTo or next parameter. If the code redirects to whatever it receives, an attacker can send users to a lookalike site after a real login. Accept only relative paths that start with a single /, and reject anything that starts with // or contains a scheme.

Check 22. Validate uploads on the server. Check file type and size server-side, not only in the browser. Rename files to random identifiers, and make sure private storage buckets do not allow public listing. This applies equally to S3, Supabase Storage, Firebase Storage, UploadThing, and Cloudflare R2.

Check 23. Restrict image proxies to known hosts. Any image optimization or proxy feature that fetches remote URLs (Next.js images.remotePatterns, Nuxt Image domains, a custom resize endpoint) should list specific hostnames. A wildcard lets anyone use your server to fetch arbitrary URLs and burn your bandwidth.

Checks 24 to 26. Rate limits and cost protection

A vibe-coded app that calls an AI model, sends email, or fires SMS is a wallet with a public URL. This category produces the most expensive failures, because the attack is boring and the bill is not.

Check 24. Rate-limit auth and expensive endpoints. Login, signup, password reset, and anything that triggers a paid API call should be limited per user and per IP. Upstash Ratelimit, Arcjet, your reverse proxy, or a few lines against Redis all work. The limit should exist before the paid call is made, not after.

Check 25. Enforce quotas on the server. Credits, plan tiers, and maximum input lengths have to be checked server-side before the request goes to OpenAI, Anthropic, Replicate, or Resend. A check in the front end can be edited out in the browser's dev tools in under a minute.

Check 26. Restrict CORS to your own domain. If Access-Control-Allow-Origin is * on an API that is not meant to be public, any website can call it from a visitor's browser. MDN's CORS guide explains the header. Lock it to your production origin unless you are deliberately shipping a public API.

Checks 27 and 28. Headers, source maps, and dependencies

These are the quickest wins on the list and the ones most often skipped because nothing visibly breaks without them.

Check 27. Set the standard security headers. Every framework and host has a place to set response headers. The baseline set is X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, a Permissions-Policy that disables camera, microphone, and geolocation unless you use them, and Strict-Transport-Security with a long max-age. MDN documents HSTS and each of the others. The free scanner at securityheaders.com grades your live site in seconds.

Check 28. Turn off production source maps and audit packages. Source maps let anyone reconstruct your unminified code, so they belong in development only. Then run npm audit, pnpm audit, pip-audit, or bundle audit depending on your stack, and read through any package your assistant added that you do not recognize. Typosquatted packages with names one letter off from the real thing are a known supply-chain risk, and an AI assistant has no way to tell the difference. The npm audit documentation covers the JavaScript side.

Running the whole checklist with your AI assistant

The same assistant that introduced these problems is good at finding them once it is told what to look for. Save this article as a markdown file in your project, then paste the prompt below into Cursor, Windsurf, Claude Code, Copilot, or whichever tool you use.

Read security-checklist.md and audit every file in this codebase against each of the 28 checks. Pay special attention to server functions, API routes, middleware, database access rules, environment variable usage, and anything that renders user content. List every finding by severity (Critical, High, Medium), cite the exact file and line, explain which check it fails, and propose the minimal code fix. Do not change any file until I approve the list.

Two things make this work better. Run it on a clean branch so you can review the diff, and run it twice, because the second pass usually catches what the first fix introduced. Treat the output as a list of leads to confirm, not a verdict.

If you would rather have a senior engineer run this against your build and fix what they find, that is part of how we launch every project. The checklist above is the same one we use, and the fixed-scope, fixed-price model means the audit is priced before it starts. For the wider risks of an AI-built site, read our breakdown of vibe-coded website security and our notes on picking a tech stack that is built to be found.

Got something to build?

Tell us about your product. We come back within 24 hours with a plan, a timeline and a fixed price.