Apps built with Lovable, Bolt, Replit, Cursor, and similar tools ship fast. That's the point. But fast-shipped apps share a predictable set of gaps — and most of those gaps are invisible until a real user triggers them. This guide covers the most common ones.
Probably not, if you haven't specifically checked. That's not an insult — it's structural. AI code generators optimize for making things work in the happy path. They scaffold a login flow that logs in. They create a database schema that stores data. They wire up a payment form that charges a card.
What they don't do well: configure the security boundaries, think about what happens when something fails, or worry about what happens when your third user opens the app at the same time as your first two.
The risks aren't theoretical. They are specific, common, and fixable — if you know they're there.
Ordered by how often we find them in free audits, not by severity.
Supabase enables RLS per table, but it defaults to off. AI-generated code often creates the tables and skips the policies. The result: any authenticated user can read every row in your database.
How to check yours
In the Supabase SQL editor, run: select tablename, rowsecurity from pg_tables where schemaname = 'public'; Every table that comes back with rowsecurity = false and holds user data is readable by anyone with your anon key — which ships in your front end. Enabling RLS is only half of it: a table with RLS on and no policies denies everything, so check pg_policies for coverage too.
More on this in the lovable teardown →If a secret key is referenced in a React component or anywhere in your frontend code, it ships to the browser. Every visitor can extract it from devtools. This includes Stripe keys, OpenAI keys, and internal service credentials.
How to check yours
Run your production build, then grep the output for recognizable prefixes: sk_live and sk_test for Stripe, service_role for a Supabase service key, eyJ for any JWT. Anything that turns up in dist/, .next/, or build/ has been public for as long as that build has been deployed — move it server-side AND rotate it, because moving it alone does not un-publish it.
More on this in the bolt teardown →Password reset, magic link, and sign-up endpoints with no rate limit are trivially brute-forced or used to spam your users. Most AI-generated auth scaffolding does not include rate limiting by default.
How to check yours
Send a burst at one endpoint and count the status codes — a hundred requests in a shell loop, piped through sort and uniq -c. All 200s means nothing is limiting you. On Supabase, also read Authentication → Rate Limits: defaults exist, but they are project-wide and sized for a busy app, so tighten them to what one legitimate user could plausibly need in an hour.
More on this in the replit teardown →A public Supabase storage bucket serves every object in it to anyone holding the URL, with no auth check at all. User-uploaded files — profile photos, documents, receipts — are on the open web unless the bucket is private and files are served through signed URLs.
How to check yours
Run select id, name, public from storage.buckets; and treat every public bucket holding user uploads as already exposed. Then confirm it: copy one object URL and open it in a private window with no session. If it renders for you, it renders for everyone. The fix is a private bucket plus short-expiry signed URLs generated server-side.
More on this in the lovable teardown →Postgres indexes primary keys automatically and foreign keys never. AI code generators create tables and queries but rarely add the missing indexes, so a query that returns instantly at 100 rows scans the whole table at 100,000.
How to check yours
Take your slowest page's query and run it with EXPLAIN ANALYZE. A Seq Scan on a table that will grow is the finding. Then check the schema more broadly: list foreign-key columns and confirm each has an index behind it. Worth doing before launch rather than after, because the symptom only appears at a data volume you cannot easily simulate later.
More on this in the lovable teardown →Stripe signs every webhook it sends. A handler that calls JSON.parse on the body instead of stripe.webhooks.constructEvent skips verification entirely and cannot tell a real event from one anybody POSTed — including a checkout.session.completed that unlocks paid features for free.
How to check yours
Grep the handler for constructEvent and confirm it reads the RAW body — verification fails against a body a framework already parsed and re-serialized. Then prove it: POST a hand-written checkout.session.completed payload to the endpoint with no signature header. A correct handler answers 400. Anything else means unsigned events are being trusted.
More on this in the cursor teardown →One thrown error in one component unmounts the entire React tree. The user gets a blank white document with no explanation and no way back — and because nothing was logged, you get no signal it happened at all.
How to check yours
Check that every route has a boundary above it: error.tsx in a Next.js App Router segment, errorElement on a React Router route, or a class component with componentDidCatch. Then prove it works — throw on purpose inside a leaf component and confirm you get a fallback UI instead of a blank page.
More on this in the bolt teardown →Work through this before you share your app publicly. Items marked critical are the ones we see cause actual incidents.
Supabase RLS enabled on every table that contains user datacritical
No secret keys referenced in frontend codecritical
Stripe webhook endpoint verifies Stripe-Signature headercritical
Storage buckets are private; user files served via signed URLscritical
Auth endpoints (login, password reset, sign-up) have rate limitingcritical
Database indexes exist on foreign keys and frequently queried columns
Error boundaries at route level — users see an error page, not a blank screen
Environment variables used for all secrets, not hardcoded stringscritical
CORS configured to reject requests from unknown origins
User-facing inputs are sanitized before database writes
The gaps above are the common set. Each builder also has its own defaults worth checking specifically.
Probably not without a specific check. AI code generators optimize for the happy path — they scaffold auth flows and database schemas, but skip security boundaries, concurrency handling, and failure paths. Work the checklist below; if you would rather someone else did, the free audit reports the same findings against your actual repo.
Lovable generates React + Supabase apps fast, but row-level security is off per table until someone enables it and writes a policy, and storage buckets are public until switched to private. Neither is part of generating a working feature, so both are commonly missing. The Lovable teardown covers the specific checks.
Row-Level Security is a Postgres feature that controls which rows each user can read and write, and it is off per table by default. An RLS check verifies that policies exist and are correctly scoped on every table holding user data. Run: select tablename, rowsecurity from pg_tables where schemaname = 'public';
Have someone who did not build it review it against a specific checklist, with real commands rather than a mental model of what should be there. Every item in this guide includes how to verify it yourself. The free audit at turtlecreekllc.com/request-audit runs the same checks against your repo and returns a severity-ranked report.
Almost all of them are a SQL query, a grep over your build output, or a curl against your deployed URL — no tooling beyond what you already have. The value of an audit is coverage and consistency: running every check on every route, every time, rather than the three you remembered.
The ones that expose data to people who never had an account: row-level security, public storage buckets, secrets that reached the client bundle, and unverified payment webhooks. Everything else costs you performance, support tickets, or a bad night — those four cost you other people's data.
Yes, though the failure modes shift. Prompt-to-app tools tend to miss configuration boundaries; AI pair-programmers tend to miss verification steps inside otherwise correct code — an unverified webhook signature, an auth middleware applied to most routes but not all, a read-modify-write with no transaction. The Cursor teardown covers that set.
The most reliable approach is to have someone who didn't build the app look at it with a specific checklist and real tools — not just a mental model of what “should” be there.
That's what the free audit is. Submit your deployed URL and a few qualifying questions. Shane reviews the app with AI-assisted scanning and human judgment, then sends back a written report with specific findings — not generic advice.
If everything looks fine, you'll know it looks fine. If something is wrong, you'll know exactly what and why.
Five questions. Written report within 48 hours. Personally reviewed by Shane Jordan. Free, no obligation.
Request Free Audit →