Lovable Codebase Audit: Security Issues, RLS Gaps, and Exposed API Keys
A client came to us last month with a Lovable-generated app that was already live in production with paying customers. They wanted us to add a few features and "clean things up a bit." What we found during our initial codebase audit was... illuminating. Exposed API keys in client-side code. Missing Row Level Security policies on tables holding user data. Direct Supabase calls with no server-side validation. And this wasn't a one-off -- we've now audited six different Lovable projects, and the patterns are remarkably consistent.
Let me be clear: Lovable is an impressive tool for prototyping. The speed at which it generates working UI from natural language prompts is genuinely remarkable. But there's a massive gap between "working prototype" and "production-ready application," and that gap is filled with security vulnerabilities that most non-technical founders don't even know to look for.
This article is a technical breakdown of what we've found across multiple Lovable codebase audits. If you've built something with Lovable and you're taking real users' money or data, you need to read this.
Table of Contents
- What Lovable Actually Generates
- The API Key Problem
- Row Level Security: The Silent Killer
- Authentication and Authorization Gaps
- Client-Side Business Logic Exposure
- Supabase Edge Functions: What's Missing
- Common Vulnerability Patterns We Found
- How to Audit Your Own Lovable Project
- When to Rebuild vs. When to Remediate
- FAQ

What Lovable Actually Generates
Lovable generates React applications (typically using Vite) with Tailwind CSS and shadcn/ui components, connected to a Supabase backend. The stack itself is solid -- we build with these same tools regularly in our Next.js development work. The problem isn't the technology choices. It's how they're wired together.
Here's a typical Lovable project structure:
src/
├── components/
│ ├── ui/ # shadcn components
│ ├── Dashboard.tsx
│ ├── Settings.tsx
│ └── ...
├── integrations/
│ └── supabase/
│ ├── client.ts # ← This is where problems start
│ └── types.ts
├── pages/
├── hooks/
└── lib/
The generated code is clean and readable. I'll give Lovable credit for that. But readability doesn't equal security. The architecture makes decisions that are fine for a demo but dangerous for production.
Let's get specific.
The API Key Problem
Every Lovable project we've audited has Supabase credentials in the client-side code. Now, before the Supabase defenders show up: yes, the anon key is designed to be public. Supabase's documentation explicitly states this. The anon key is meant to be used in client-side code, and security is supposed to be enforced through Row Level Security policies.
But here's where it gets ugly.
In three of the six projects we audited, we found the Supabase service_role key either:
- Hardcoded directly in a utility file
- Stored in an
.envfile that was committed to the GitHub repo - Referenced in a Supabase Edge Function that was accessible without authentication
The service_role key bypasses all RLS policies. If someone gets it, they have full read/write access to your entire database. Every table. Every row. Every user's data.
// Actual pattern we found in a Lovable project (keys redacted)
import { createClient } from '@supabase/supabase-js'
// This was in a file called lib/admin.ts
// imported and used in a client-side component
const supabaseAdmin = createClient(
'https://xxxxx.supabase.co',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' // service_role key!
)
This wasn't generated by Lovable directly -- it was added by the founder when they needed admin functionality and asked Lovable to "add an admin panel." Lovable complied, and since it doesn't have a concept of server-side vs. client-side security boundaries, it put the key where it was convenient.
Even when only the anon key is exposed (as intended), the security model falls apart if RLS isn't configured correctly. Which brings us to the big one.
Row Level Security: The Silent Killer
Row Level Security (RLS) is Supabase's primary security mechanism for protecting data at the database level. When configured correctly, it ensures users can only access their own data regardless of what API calls are made from the client.
When we audited the six Lovable projects, here's what we found:
| Project | Tables Total | Tables with RLS Enabled | Tables with Correct RLS Policies | Sensitive Data Exposed |
|---|---|---|---|---|
| SaaS Dashboard | 14 | 6 | 3 | User PII, billing data |
| E-commerce App | 22 | 10 | 4 | Order history, addresses |
| Health Tracker | 11 | 4 | 2 | Health records, medications |
| Project Manager | 18 | 8 | 5 | Client data, documents |
| Booking Platform | 16 | 7 | 3 | Contact info, schedules |
| CRM Tool | 20 | 9 | 4 | Customer data, notes |
Read that again. In the health tracker app -- which stored actual medication and health information -- only 2 out of 11 tables had correct RLS policies. Someone with the anon key (which is public, remember) could query every user's health records.
The most common RLS failures we see:
Missing Policies Entirely
Lovable often creates tables without enabling RLS at all. In Supabase, RLS is disabled by default on new tables, which means anyone with the anon key can read all data.
-- What we often find: RLS not even enabled
CREATE TABLE public.user_profiles (
id UUID REFERENCES auth.users,
full_name TEXT,
email TEXT,
phone TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- No ALTER TABLE ... ENABLE ROW LEVEL SECURITY;
-- No policies defined
Overly Permissive Policies
When Lovable does add RLS, the policies are often too broad:
-- Common Lovable-generated policy
CREATE POLICY "Users can view all profiles"
ON public.user_profiles
FOR SELECT
USING (true); -- This allows ANY authenticated user to read ALL profiles
The fix should be:
-- What it should look like
CREATE POLICY "Users can view own profile"
ON public.user_profiles
FOR SELECT
USING (auth.uid() = id);
Missing DELETE and UPDATE Policies
Even when SELECT policies are correct, INSERT/UPDATE/DELETE policies are frequently missing or wrong. We found cases where any authenticated user could update any other user's profile.

Authentication and Authorization Gaps
Lovable handles basic authentication reasonably well -- it sets up Supabase Auth with email/password or social logins, and the login/signup flows generally work. But authentication (who are you?) and authorization (what can you do?) are different things.
The authorization layer is almost always incomplete or nonexistent.
Consider a multi-tenant SaaS app. Users belong to organizations. They should only see data from their organization. They might have different roles (admin, member, viewer). Lovable generates none of this.
What we typically find:
// Lovable-generated data fetching
const { data: projects } = await supabase
.from('projects')
.select('*')
// No filter for organization_id
// No check for user's role or permissions
The fix requires both database-level (RLS) and application-level changes. You need a memberships table mapping users to organizations with roles, RLS policies that check membership, and application code that filters appropriately.
This is the kind of architecture work that's hard to bolt on after the fact. It touches every query, every component, every page. If you're building a multi-tenant SaaS with Lovable, this is the thing that will bite you hardest.
Client-Side Business Logic Exposure
Because Lovable generates pure client-side React apps, all business logic lives in the browser. This means:
- Pricing calculations are visible and manipulable in browser DevTools
- Feature flags for different subscription tiers are checked client-side
- Discount codes and validation logic are in the JavaScript bundle
- API rate limiting doesn't exist (there's no server to enforce it)
We found one Lovable-generated SaaS where the pricing tier check was entirely client-side:
// Found in a Lovable project's component
const canAccessFeature = (feature: string) => {
const plan = user?.subscription?.plan
if (plan === 'pro') return true
if (plan === 'basic' && BASIC_FEATURES.includes(feature)) return true
return false
}
A user could simply modify this function in the browser console -- or directly call the Supabase API without this check -- and access pro features on a basic plan. The database had no policies enforcing plan-based access.
This is a fundamental architectural problem. Business logic needs a server-side component. Whether that's Next.js API routes, Supabase Edge Functions, or a separate backend service -- something needs to validate operations where the user can't tamper with it.
This is exactly why we often recommend frameworks like Next.js or Astro for production SaaS applications -- they give you server-side rendering and API routes out of the box.
Supabase Edge Functions: What's Missing
Some Lovable projects do use Supabase Edge Functions for certain operations -- typically Stripe webhook handling or sending emails. But the implementation often has issues:
No input validation: Edge Functions accept and process whatever JSON is sent to them without validating shape, types, or constraints.
CORS configured to allow all origins:
// Common pattern in Lovable Edge Functions
const corsHeaders = {
'Access-Control-Allow-Origin': '*', // Allows any website to call this
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}
No authentication check: The function doesn't verify the JWT token, so unauthenticated users can call it.
Stripe webhook signatures not verified: In two projects, the Stripe webhook handler didn't verify the webhook signature, meaning anyone could send fake payment events to the endpoint.
// What we found -- no signature verification
Deno.serve(async (req) => {
const body = await req.json()
// Directly processes the event without verifying it came from Stripe
if (body.type === 'checkout.session.completed') {
// Update user's subscription
await supabaseAdmin.from('subscriptions').update({
status: 'active',
plan: 'pro'
}).eq('user_id', body.data.object.metadata.user_id)
}
})
This means an attacker could send a POST request to the webhook URL with a fake checkout.session.completed event and upgrade any user to a pro plan for free.
Common Vulnerability Patterns We Found
Here's a summary of the most common issues ranked by severity and frequency:
| Vulnerability | Severity | Frequency (out of 6) | Exploitability |
|---|---|---|---|
| Missing RLS on sensitive tables | Critical | 6/6 | Easy -- just query the table |
| Overly permissive RLS policies | High | 6/6 | Easy with anon key |
| Service role key exposure | Critical | 3/6 | Trivial if found |
| No Stripe webhook verification | High | 4/6 | Medium -- need endpoint URL |
| Client-side authorization only | High | 6/6 | Easy with DevTools |
| No input validation on Edge Functions | Medium | 5/6 | Medium |
| CORS wildcard on Edge Functions | Medium | 5/6 | Easy |
| Sensitive data in localStorage | Medium | 4/6 | Physical access or XSS |
| No rate limiting | Medium | 6/6 | Trivial |
| Insecure direct object references | High | 5/6 | Easy -- change ID in URL |
How to Audit Your Own Lovable Project
If you've built something with Lovable that's handling real user data, here's how to check for these issues yourself.
Step 1: Check Your Supabase RLS Status
Go to your Supabase dashboard → Table Editor. Click on each table and check if RLS is enabled. Then go to Authentication → Policies and review each policy.
Or run this query in the SQL Editor:
SELECT
schemaname,
tablename,
rowsecurity
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY tablename;
If rowsecurity is false for any table containing user data, that's a critical issue.
Step 2: Search for Exposed Keys
In your codebase, search for:
grep -r "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" src/
grep -r "service_role" src/
grep -r "SUPABASE_SERVICE" src/
The first pattern matches the beginning of Supabase JWT keys. If you find the service_role key anywhere in your src/ directory, that's an immediate critical vulnerability.
Step 3: Test RLS Policies
Create a second test user account. Log in as that user and try to access data from user one. Check the browser's Network tab -- are you receiving data you shouldn't?
You can also test directly with curl:
curl 'https://YOUR_PROJECT.supabase.co/rest/v1/user_profiles?select=*' \
-H "apikey: YOUR_ANON_KEY" \
-H "Authorization: Bearer YOUR_ANON_KEY"
If this returns all user profiles without authentication, RLS is broken.
Step 4: Check Edge Functions
Review each Edge Function for:
- JWT verification
- Input validation
- CORS configuration
- Webhook signature verification (for Stripe/payment handlers)
Step 5: Inspect Client-Side Bundle
Run npm run build and inspect the output. Search the built JavaScript for API keys, business logic, and pricing data. Anything in the bundle is visible to users.
When to Rebuild vs. When to Remediate
This is the question every Lovable founder faces once they realize these issues exist. The answer depends on several factors:
Remediate if:
- Your app has fewer than 10 tables
- You don't have a complex authorization model (no multi-tenancy, no roles)
- The core architecture (data model, page structure) is sound
- You mainly need to add RLS policies and move some logic server-side
Rebuild if:
- You need multi-tenant architecture with roles and permissions
- Your business logic is complex and all client-side
- You need server-side rendering for SEO or performance
- The Supabase schema has significant structural issues
- You're at a scale where you need proper infrastructure
For remediation, you're typically looking at adding RLS policies across all tables, migrating sensitive logic to Edge Functions or a server-side layer, implementing proper input validation, and adding rate limiting. This is a 2-4 week project for an experienced developer depending on complexity.
For a full rebuild, we typically recommend a headless architecture with proper separation of concerns. It costs more upfront but gives you a foundation that scales. Check our pricing page for a sense of what that looks like.
If you're unsure which path is right, we're happy to do a quick assessment. Hit us up on our contact page.
FAQ
Is Lovable safe to use for production applications? Lovable can generate a solid starting point, but the output needs significant security hardening before it's production-ready. The generated code lacks proper RLS policies, server-side validation, and authorization logic. Think of it as scaffolding, not the finished building. You absolutely need a developer to review and secure the code before real users trust it with their data.
Does Lovable expose my Supabase API keys?
The Supabase anon key is intentionally public -- that's by design, and Supabase's security model accounts for it through RLS. The problem is when Lovable (or you, through prompts) places the service_role key in client-side code. The anon key being public is only safe if your RLS policies are bulletproof, which in Lovable-generated projects, they typically aren't.
What is Row Level Security and why does it matter? Row Level Security (RLS) is a PostgreSQL feature that Supabase uses to control which rows a user can read, insert, update, or delete. Without RLS, anyone with your public anon key can query your entire database -- every user's data, every private record. It's the single most important security mechanism in a Supabase-backed application, and it's the single most commonly misconfigured thing in Lovable projects.
Can I fix Lovable security issues myself without a developer? If you understand SQL and Supabase's RLS policy syntax, you can add basic RLS policies yourself using the Supabase dashboard. However, getting the policies right -- especially for complex scenarios like multi-tenancy, shared resources, or admin access -- requires experience. A wrong policy can either lock users out of their own data or leave everything exposed. For anything beyond a simple personal project, get professional eyes on it.
How do I check if my Lovable app's database is secure?
The quickest test: open your browser's DevTools, go to the Network tab, and look at the Supabase API calls your app makes. Copy the apikey header value. Then use curl or Postman to query your tables directly using just that key with no auth token. If you get back other users' data -- or any data at all on tables that should be private -- your RLS is broken.
What are the biggest security risks with AI-generated code in general? AI code generators optimize for making things work, not for making things secure. They don't have a mental model of your threat landscape. The biggest risks are: exposed secrets, missing input validation, overly permissive access controls, and the absence of server-side security boundaries. These aren't unique to Lovable -- similar issues exist in code from Cursor, v0, Bolt, and other AI tools. The difference is Lovable generates full applications that people deploy directly to production.
Should I switch from Supabase to a different backend after using Lovable? Supabase itself is fine. It's a solid platform with proper security capabilities. The issue is how Lovable configures it. You don't need to abandon Supabase -- you need to properly configure RLS policies, move sensitive operations to Edge Functions, and add the authorization layer that Lovable skipped. The infrastructure is good; the configuration just needs work.
How much does it cost to fix security issues in a Lovable-generated app? For a straightforward remediation -- adding RLS policies, securing Edge Functions, removing exposed keys, adding basic input validation -- you're looking at roughly $3,000-$8,000 depending on the number of tables and complexity of your authorization model. A full rebuild with proper architecture runs $15,000-$50,000+ depending on scope. The remediation path is almost always more cost-effective if your core data model is sound.