What AI-built apps get wrong, and how to check yours
AI builders like Lovable, Bolt, v0 and Replit, and coding agents like Cursor and Claude Code, ship working apps in hours. But usually nobody reviews the code they write. The gaps that come up again and again are open database tables, secret keys in the browser, pages search engines can't read, and code that gets harder to change with every prompt.
Last reviewed by the Forge team
What goes wrong
What it costs when this is missed
Critical
Data open to anyone
A Supabase or Firebase table without access rules can be read or changed by anyone holding the public key that ships with every page.
Critical
Secret keys in the browser
Anything the frontend can use, a visitor can copy. An AI, Stripe or service key in client code is a bill or a breach waiting to happen.
High
Pages search engines can't read
A client-rendered page sends crawlers an empty shell. Titles, text and links only appear after JavaScript runs.
Medium
Code that fights every change
Each prompt adds code next to the old code instead of changing it. A few months in, every fix breaks something else.
Common problems
What to look for in your code
Each one: why it hurts, and how to spot it in your own project.
01Tables without row-level security
Supabase's anon key is public by design. Row-level security (RLS) is what stops one user from reading another user's rows. In 2025 researchers found tables open to the anon key in 170 of 1,645 apps from Lovable's public showcase (CVE-2025-48757). Lovable disputes the CVE, saying each app's owner is responsible for their data. Either way, the fix is yours to make.
How to spot it: In Supabase, check that every table in the public schema has RLS enabled and at least one policy. Then try to read another user's row using only the anon key.
Risky
create table profiles (
id uuid primary key,
email text,
plan text
);
-- RLS never enabled: the anon key reads every row
Better
alter table profiles enable row level security;
create policy "read own profile" on profiles
for select using ((select auth.uid()) = id);
02Secret keys in frontend code
Builders often call an API straight from the page. Any key used in code that runs in the browser ends up in the JavaScript every visitor downloads.
How to spot it: Search the code for sk_, sk-, service_role and dangerouslyAllowBrowser. Check that variables starting with VITE_ or NEXT_PUBLIC_ hold nothing secret.
Risky
const openai = new OpenAI({
apiKey: import.meta.env.VITE_OPENAI_KEY,
dangerouslyAllowBrowser: true,
});
Better
// the page calls your server; the key never leaves it
const res = await fetch("/api/summarise", {
method: "POST",
body: JSON.stringify({ text }),
});
03Permission checks only in the UI
Hiding a button isn't a permission check. If the API route or the database doesn't check who is calling, anyone can call it directly with the browser's dev tools.
How to spot it: For every write (update, delete, payment, invite), find where the server checks the user. If the only check is in a React component, the check is missing.
export async function deleteUser(id: string) {
const me = await requireUser();
if (!me.isAdmin) throw new Forbidden();
await db.users.delete(id);
}
04Pages rendered only in the browser
Older Lovable projects (React + Vite), and many Bolt and hand-prompted Vite projects, are single-page apps: the HTML holds an empty root element. Lovable now server-renders new apps (TanStack Start, since May 2026) and pre-renders older ones for verified crawlers. Other crawlers and many SEO tools still get the empty shell.
How to spot it: Use curl or 'View page source' (not the dev tools, which show the page after JavaScript ran). If the title, main text and links aren't in it, your pages depend on JavaScript to be read.
<body>
<h1>Invoices for small teams</h1>
<p>Send, track and get paid…</p>
<a href="/pricing">Pricing</a>
</body>
05One title, no sitemap, no robots.txt
Generated apps often keep the starter's title on every page and ship without sitemap.xml or robots.txt, so search engines can't tell the pages apart or find them all.
How to spot it: Open /robots.txt and /sitemap.xml on your live site. Then check that every public page has its own title and description.
06The same thing built three times
Agents often write a new component or helper instead of finding the existing one. The copies drift apart, and a fix lands in only one of them.
How to spot it: Look for near-identical names (Button, Button2, NewButton), several date or price formatters, and repeated fetch calls to the same endpoint.
07Huge files and no tests
A 1,500-line page that does everything is hard to change, for you and for the next prompt. Without tests, nobody notices when a prompt breaks sign-up or checkout.
How to spot it: List your largest files. Check that login, payments and data writes each have at least one test that would fail if they broke.
08Packages nobody chose
Generated projects pick up unused or overlapping packages, and models sometimes suggest package names that don't exist. Attackers register those names ('slopsquatting').
How to spot it: Check that each package in package.json is imported somewhere, is the well-known package you expect, and is maintained.
Quick self-check
How does your project do?
Tick each one that's true today. Anything left unticked is worth a closer look.
Tick what's true for your project. 8 checks.
How Forge checks this
A second opinion on your code, from the Issues, SEO and Structure scans.
Reads the code your builder generated (synced to GitHub and cloned to your computer), not just the live page.
Checks routes, server actions and database access for missing permission checks, and the code for secrets (Issues scan).
Fetches your live pages and compares them with the code: titles, robots, sitemap, rendering (SEO scan).
Finds duplication, oversized files and dead code before they slow you down (Structure scan).
Example findingIssuesHighConfirmed in codeProfiles table readable with the public keysupabase/migrations/0001_init.sql:4create table profiles (row-level security is never enabled for this table in any migration
Questions
Are Lovable, Bolt or v0 bad for SEO?
Not in themselves. v0 builds Next.js apps, which render on the server by default. Lovable server-renders new apps (since May 2026) and pre-renders older ones for verified crawlers. Bolt and coding agents build whatever stack you ask for. What matters is the HTML your pages actually send: check it with 'View page source' or curl.
Is the Supabase anon key a secret?
No. It's meant to be public, which is exactly why every table needs row-level security. The service_role key is the secret one: it bypasses RLS and must never reach the browser.
Can't I just ask the AI to review its own code?
It helps, but the same model tends to repeat its own assumptions. A separate review that follows a fixed checklist and has to point at real lines of code catches what the author missed.
Do I need the code to check my app?
For a real check, yes. A look at the live site can't see database rules or server routes. Most builders can sync your project to GitHub; clone it and review the folder.