Fix.
The vibe coded MVP audit checklist
A practical 6-point checklist to find hidden security flaws, exposed secrets, and database performance traps in AI-generated web applications.
Modern AI tools make it possible to build an interactive web application in hours. Prompts produce working dashboards, authentication flows, and Stripe checkouts without writing boilerplate code by hand.
The issue is that AI models optimize for visual completion and immediate user feedback, not production security or edge case reliability. When early testers arrive, common architectural shortcuts create silent security breaches or crash database connections.
Before you launch an AI-generated product to paying customers, run your codebase through this six-point audit.
1. Inspect client bundles for exposed administrative keys
The single most dangerous failure in AI-generated full-stack software is embedding administrative credentials directly into client JavaScript.
AI tools frequently import backend clients into frontend components to make features work quickly. Inspect your built assets in dist/assets or your browser developer console:
- Supabase
service_rolekey: This key bypasses Row-Level Security entirely. If it appears anywhere in your client files, any visitor can open their browser console and delete your entire database. - Stripe restricted secret keys: Client checkouts should only use the publishable key (
pk_live_...). A secret key (sk_live_...) in frontend code lets attackers issue unauthorized refunds or read transaction histories. - Third-party API tokens: OpenRouter, OpenAI, or Resend keys must always execute behind backend edge functions with user rate limits.
Run a search across your repository for service_role and sk_live. If found in files under src/components or src/pages, move the database logic to an edge function immediately.
2. Verify Row-Level Security on every public table
AI builders routinely create database tables without activating Row-Level Security (RLS), or generate blanket policies that permit anyone to read and write.
Run this query in your Supabase SQL editor to inspect all tables:
select
tablename,
rowsecurity
from pg_tables
where schemaname = 'public';
If rowsecurity is false on any table, anyone who discovers your public Supabase URL and anon key can query that table directly using the Supabase JavaScript client, completely bypassing your frontend interface.
For every table containing customer data, enable RLS:
alter table profiles enable row level security;
alter table payments enable row level security;
Then write specific policies for select, insert, update, and delete. Never rely on a single blanket policy for all operations.
3. Harden Stripe webhooks against forged payloads
Many AI prototypes mark invoices as paid based solely on client-side redirect query parameters (such as ?payment=success).
Attackers can trigger premium account upgrades simply by navigating directly to the success URL without paying a cent.
Production billing must rely exclusively on verified server-side webhooks:
- Never trust client redirects for account provisioning.
- Read the raw request body in your webhook handler.
- Validate the
stripe-signatureheader against your production webhook signing secret (whsec_...). - Reject requests where verification fails before updating database records.
Verify that your webhook endpoint is idempotent. If Stripe retries a webhook notification three times, your backend must not credit user accounts three times.
4. Eliminate unbounded queries and missing indexes
AI-generated database queries almost never include pagination limits. While a query like supabase.from('posts').select('*') runs fine with 10 test records, it crashes browser memory when your database reaches 5,000 records.
Audit your data fetching queries:
- Ensure every list query contains
.limit(50)and explicit offset or cursor pagination. - Filter only the exact columns required rather than using
select('*'). - Check foreign key columns. Postgres does not index foreign keys automatically. Add indexes to columns used in filters and joins (such as
user_idandcreated_at) to prevent full table scans.
5. Validate client-side permission guards on the server
In prototype applications, AI models often hide administrative buttons by checking a user’s role in local state:
// Fragile: Client-only guard
{user.role === 'admin' && <DeleteUserButton />}
Hiding a button does not protect the underlying database record. Anyone can inspect the network request, copy the API endpoint, and send a direct DELETE request.
Every destructive operation must enforce permission checks on the database or edge function layer. The database must refuse the query even if a malicious user manipulates frontend state.
6. Configure error boundaries and connection fallbacks
When an external API times out or returns an unexpected schema, AI-generated React code frequently crashes into a white screen of death.
- Wrap major routes in React error boundaries with human-readable error messages and retry buttons.
- Handle database connection drops gracefully with exponential backoff.
- Replace infinite loading spinners with explicit timeouts that notify users when a service is unreachable.
Getting an independent assessment
Auditing your own application can be difficult when launch deadlines are approaching.
If you have a prototype built with Lovable, Cursor, or Bolt that is heading to production, we provide an external technical diagnosis within 48 hours to pinpoint security vulnerabilities and architecture bottlenecks before your users encounter them.
More guides on fix
- How to fix infinite recursion in Supabase RLS6 min read
- Taking over a codebase someone else built6 min read
- Lovable Cloud to Supabase migration, what it actually involves7 min read
- What it actually costs to fix a vibe-coded app6 min read
- Is your Lovable app ready for real users? Ten checks8 min read
- Supabase RLS policies that actually work7 min read
- Your Supabase service role key is in your frontend. Do this now5 min read
- Check whether your Supabase data is public in five minutes5 min read
- Why your Lovable app shows a blank page after deploy6 min read