Audit AI-Built MVPs Before First Paying Users
Cursor and v0 ship demos that work. They also ship the same access-control bugs attackers try first.
AI coding tools get founders to a working demo faster than any previous generation of tooling. Cursor, v0, Lovable, and Bolt produce route handlers that compile, pass a casual review, and look finished in a browser. That is exactly the problem. The models optimize for "happy path works." They do not optimize for multi-tenant isolation, and the bugs they leave behind are consistent enough that you can almost predict them from the original prompt.
The attack surface has not changed. What changed is how often the same classes of mistakes land in production-shaped code without a human ever writing the auth path. Ship a Cursor- or v0-built MVP without a deliberate access-control pass and you are not taking a calculated risk. You are running a public BOLA lab for the first bored person who iterates /api/items/1 to /api/items/2.
The three bugs AI keeps writing
Across reviews of AI-generated MVPs, three failure modes show up with high frequency. They are not exotic. They are the defaults of "make it work" generation.
Missing tenant scope (BOLA / IDOR). A typical prompt asks for a route that fetches a project by ID. The model usually adds a session check and then queries by primary key alone. Any authenticated user can read any other user's row by guessing or walking IDs. The auth gate is present. The ownership gate is not. That pattern is the top finding in indie-dev pentests, and it is the class behind the Lovable disclosure (CVE-2025-48757) that exposed more than 170 generated apps, including one holding about 13,000 user records. The fix is usually a single predicate on the query (ownerId or orgId tied to the session) and a 404 when the row does not match. Models skip that clause unless the system prompt or rules file forces it every time.
Supabase tables without RLS. New Supabase tables ship with Row-Level Security off. The anon key lives in the client bundle, so it is public by design. Without an explicit policy, that key can read or write whatever the PostgREST layer exposes. Independent analyses put the rate of this misconfiguration very high: one 2025 sample of exposed Supabase instances found it in 83% of cases; community audit reports on AI-built Supabase projects put disabled or overly permissive RLS around 70%. Either number is enough to treat "RLS on, policies reviewed" as a launch gate rather than a nice-to-have.
Server secrets in the client bundle. Next.js-style apps make it easy for a server helper that touches process.env.STRIPE_SECRET_KEY or a service-role key to get imported into a Client Component through a convenient path. Build output then ships the secret to every visitor. Cursor-era projects also leak .env material through Composer history or accidental commits. The same class shows up as hardcoded keys in source and as service-role keys used from the browser.
A separate systematic pass across 15 apps generated with AI coding tools (including Cursor) reported 69 vulnerabilities. The distribution matches what the three classes above predict: access control, secret handling, and weak server-side validation. Functionality is not the bottleneck. Isolation is.
The afternoon audit that actually matters
You can eliminate the highest-severity, externally visible failures in a few hours without a full pen test. Treat these as binary gates.
1. Public surface of the API. Against a staging URL, hit the obvious authenticated paths without credentials:
APP=https://your-mvp.example
for path in /api/me /api/users /api/projects /api/admin /api/settings; do
code=$(curl -s -o /dev/null -w "%{http_code}" "$APP$path")
echo "$path -> $code"
done
Anything that returns 200 without a session needs an explicit product decision, not an assumption.
2. Cross-user access (the BOLA check). Create two accounts. From B's session, request a resource owned by A:
curl -s -H "Cookie: $B_SESSION" \
"$APP/api/projects/$A_PROJECT_ID" -w "\n%{http_code}\n"
403 or 404 is acceptable. 200 with A's data is a ship blocker. Walk every object type that has an ID in the URL or body: projects, documents, invoices, team invites. If the model wrote findUnique({ where: { id } }) without a tenant predicate, you will find it here.
3. Supabase with only the anon key.
curl -s "$SUPA_URL/rest/v1/users?select=*" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ANON_KEY" | head -c 500
If you see rows a stranger should never see, RLS is missing or wrong. Repeat per table. Prefer auth.uid()-scoped policies over "authenticated can do everything." Move truly sensitive tables behind a server-only API if a safe policy is hard to express.
4. Secrets in build output.
npm run build
grep -rE "sk_(test|live)_|service_role|PRIVATE_KEY|xoxp-|ghp_" .next/static/ public/ \
&& echo "SECRET FOUND" || echo "Clean"
Add import 'server-only' at the top of every module that touches a secret so a client import fails the build instead of shipping quietly.
5. Headers and auth shape. Check for CSP, HSTS, X-Frame-Options, and X-Content-Type-Options. Confirm auth is enforced on the server for every protected route, not only in client components that a user can skip. Client-only auth checks are high frequency in AI-generated frontends and trivial to bypass.
If all five are green, you have removed the bugs most likely to turn a launch post into a disclosure thread within the first months of real traffic.
What curl cannot see
External checks miss chained issues: duplicated business rules, N+1 query patterns that only hurt under load, missing input validation that becomes XSS or injection once the UI is scraped, and error paths that dump stack traces or PII. Pre-launch readiness also needs working sign-up and password reset for a stranger, a tested rollback (promote a previous deploy once on purpose), error monitoring that actually pages you, and for AI features, rate limits plus a hard spend cap so one bot or retry loop cannot produce a surprise bill.
Do not ask the same model that wrote the code to grade itself as the only review. Separate review works better: static rules in .cursor/rules (always scope queries by tenant, never hardcode secrets, always enable RLS), PR-time semantic review where available, and targeted agent fixes after a human names the file and the failure. Free dependency scanners (npm audit) catch library issues; they will not catch a missing ownerId clause.
Keep, harden, or rebuild
Not every AI MVP needs a rewrite. Keep the foundation when the data model still matches the business, core money and auth flows are stable, new features take a predictable amount of time, and someone can explain where the critical rules live without a scavenger hunt. In that case, add tests around the revenue path, document the auth model, and fix the three bug classes above.
Refactor when product direction is validated but duplicated logic, fragile integrations, or weak tests are slowing every change. Rebuild when the MVP was demo architecture, the business model shifted, or permissions and tenancy are so improvised that every feature risks another BOLA. Redesign first if users cannot complete the core flow even when the code is "correct." A 30/60/90 split works: stabilize and audit, choose the path, then either harden or replace with discipline.
The tools did their job when they got you to learning. Production multi-tenancy is still your job. The models will keep omitting the ownership clause until the prompt, rules, and review process stop them. Run the BOLA check before the first hundred users, not after the disclosure email.
Sources & further reading
- Audit a Cursor or v0-Built MVP Before You Launch — dev.to
- Cursor Code Audit: Review AI-Generated Code (2026) — vibecoding.app
- Built a small launch-readiness service for AI-built MVPs — looking for feedback - Built for Cursor - Cursor - Community Forum — forum.cursor.com
- Keep, Refactor, or Rebuild Your AI-Built MVP? | Shakuro — shakuro.com
- MVP Pre-Launch Checklist | SpeedMVPs | SpeedMVPs — speedmvps.com
Ji-ho covers the increasingly tangled overlap between cloud architecture and security, drawing on a background as a penetration tester to keep his reporting grounded in real-world attack paths. He never lets a vendor claim go unquestioned and insists that every buzzword come with a proof of concept.
Discussion 0
No comments yet
Be the first to weigh in.