How to Secure Lovable Apps: Fix the Vulnerabilities AI Builders Miss

Written by the Rafter Team

Lovable generates over 750,000 apps per month. It is one of the fastest-growing AI app builders in the world, and for good reason — you describe what you want, and Lovable gives you a working full-stack application in minutes. But speed creates blind spots. A Rafter analysis of Lovable-generated codebases found that 10.3% contain critical Row-Level Security flaws — misconfigurations that expose entire database tables to anyone with a browser and a cURL command. That number doesn't include hardcoded secrets, missing input validation, or insecure dependency chains. This guide covers the most common Lovable app vulnerabilities and exactly how to fix them.
If you've deployed a Lovable app with Supabase and never manually configured RLS, your database may be publicly readable right now. Run this query in the Supabase SQL Editor to check: SELECT tablename FROM pg_tables WHERE schemaname = 'public' AND NOT rowsecurity;
Why Lovable Apps Need Extra Security Attention
Lovable scaffolds applications using Supabase as the default backend, React (via Vite) for the frontend, and Tailwind CSS for styling. This stack is solid — but Lovable's AI optimizes for getting your app working, not for getting your app secure. The security gaps are predictable and fixable once you know where to look.
Three factors make Lovable apps particularly exposed:
-
Client-direct database access. Supabase exposes your PostgreSQL database directly to the browser via PostgREST. There is no application server filtering requests. If RLS is misconfigured, the browser has direct read/write access to every row.
-
AI-generated security policies. When Lovable does generate RLS policies, they frequently contain logic errors — overly broad
USINGclauses, missingWITH CHECKconstraints, or policies that referenceauth.uid()without handling the unauthenticated case. -
Default-open configuration. Lovable projects ship with the Supabase
anonkey in client-side code (which is correct), but without the RLS policies that make that architecture safe (which is dangerous).
The result: apps that work perfectly in demos but leak data in production.
The 5 Most Common Lovable App Vulnerabilities
1. Missing Row-Level Security (Critical)
This is the most dangerous and most common flaw. When RLS is disabled on a Supabase table, the anon key — which is embedded in every client-side bundle and visible to anyone — grants unrestricted access to every row.
What it looks like:
-- Table created by Lovable without RLS
CREATE TABLE public.user_profiles (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES auth.users(id),
full_name TEXT,
email TEXT,
plan TEXT DEFAULT 'free'
);
-- No ALTER TABLE ... ENABLE ROW LEVEL SECURITY
-- No CREATE POLICY statements
What an attacker can do:
# Read every user profile with just the anon key
curl 'https://your-project.supabase.co/rest/v1/user_profiles?select=*' \
-H "apikey: eyJ..." \
-H "Authorization: Bearer eyJ..."
# Delete all records
curl -X DELETE 'https://your-project.supabase.co/rest/v1/user_profiles?id=not.is.null' \
-H "apikey: eyJ..." \
-H "Authorization: Bearer eyJ..."
The fix:
-- 1. Enable RLS on every public table
ALTER TABLE public.user_profiles ENABLE ROW LEVEL SECURITY;
-- 2. Add a policy that restricts access to the row owner
CREATE POLICY "Users can view their own profile"
ON public.user_profiles
FOR SELECT
USING (auth.uid() = user_id);
CREATE POLICY "Users can update their own profile"
ON public.user_profiles
FOR UPDATE
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
-- 3. Deny all access by default (no INSERT/DELETE policies = no access)
Audit every table at once:
SELECT tablename
FROM pg_tables
WHERE schemaname = 'public'
AND NOT rowsecurity;
Any table in the results is fully exposed. Enable RLS on all of them.
2. Overly Permissive RLS Policies (High)
Some Lovable apps do have RLS enabled — but the policies grant access to everyone. This is sometimes worse than no RLS at all, because it creates a false sense of security.
What it looks like:
-- Lovable-generated "security" policy that does nothing
CREATE POLICY "Enable read access for all users"
ON public.orders
FOR SELECT
USING (true); -- ← This allows ANY request to read ANY row
The fix: Replace true with an actual access condition:
-- Only the customer who placed the order can view it
DROP POLICY "Enable read access for all users" ON public.orders;
CREATE POLICY "Customers can view their own orders"
ON public.orders
FOR SELECT
USING (auth.uid() = customer_id);
Common patterns for correct policies:
| Table Type | USING Clause |
|---|---|
| User-owned data | auth.uid() = user_id |
| Public read, owner write | SELECT: true, UPDATE/DELETE: auth.uid() = owner_id |
| Team-shared data | auth.uid() IN (SELECT member_id FROM team_members WHERE team_id = teams.id) |
| Admin-only | auth.uid() IN (SELECT id FROM profiles WHERE role = 'admin') |
3. Service Role Key in Client Code (Critical)
The Supabase service_role key bypasses all RLS policies. If it appears anywhere in client-side code, your database is fully exposed regardless of your RLS configuration.
What it looks like:
// ✗ Lovable sometimes generates this in lib/supabase.ts
const supabase = createClient(
import.meta.env.VITE_SUPABASE_URL,
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' // service_role key!
);
How to check: Search your codebase for service_role or for any JWT that isn't your anon key. In your Supabase dashboard, go to Settings > API and compare the keys.
The fix: Client-side code must only use the anon key. If you need the service_role key for admin operations, use it exclusively in Supabase Edge Functions or a server-side API route:
// ✓ Client-side: anon key only
const supabase = createClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY
);
// ✓ Edge Function: service_role key is safe here
import { createClient } from '@supabase/supabase-js';
const adminClient = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
);
4. Hardcoded API Keys and Secrets (High)
Lovable's AI frequently generates code with API keys, database URLs, and third-party service tokens hardcoded directly in source files. These end up in your GitHub repository and are indexed by search engines and secret scanners within minutes.
What it looks like:
// ✗ Common in Lovable-generated integration code
const stripe = new Stripe('sk_live_51abc...', { apiVersion: '2023-10-16' });
const resend = new Resend('re_abc123...');
const openai = new OpenAI({ apiKey: 'sk-proj-abc123...' });
The fix:
- Move all secrets to environment variables:
// ✓ Use environment variables
const stripe = new Stripe(import.meta.env.VITE_STRIPE_KEY);
- Add secrets to
.envand ensure.envis in.gitignore - If secrets were already committed, rotate them immediately — git history preserves the old values even after deletion
5. Missing Input Validation (Medium)
Lovable generates form handlers and API routes that trust user input without validation. This opens the door to XSS, SQL injection (through Supabase RPC calls), and data corruption.
What it looks like:
// ✗ No validation on user-submitted data
const { data } = await supabase
.from('comments')
.insert({ body: userInput, author: userName });
The fix: Validate and sanitize all user input before it reaches your database or renders in the DOM:
import { z } from 'zod';
const commentSchema = z.object({
body: z.string().min(1).max(2000).trim(),
author: z.string().min(1).max(100).trim(),
});
// ✓ Validated before insert
const parsed = commentSchema.parse({ body: userInput, author: userName });
const { data } = await supabase.from('comments').insert(parsed);
How to Scan Your Lovable App with Rafter
You don't need to find these vulnerabilities manually. Rafter scans your codebase automatically and generates AI-ready fix prompts that you can paste directly into Lovable. The condensed workflow lives below; if you'd rather follow a screenshot-by-screenshot walkthrough, our 5-minute Lovable security audit guide covers the same flow in more detail.
Step 1: Connect Your Lovable Project to GitHub
- Open your project in Lovable
- Click the GitHub icon next to "Publish"
- Select "Connect GitHub" and authorize the integration
This syncs your Lovable codebase to a GitHub repository. Rafter reads your code from GitHub using read-only permissions.
Step 2: Run a Rafter Scan
- Go to rafter.so
- Sign in with GitHub
- Select your Lovable project repository
- Click Start Scan
Rafter analyzes your codebase for the vulnerabilities described above — missing RLS, exposed secrets, insecure dependencies, and unsafe code patterns. Most Lovable projects scan in under a minute.
Step 3: Apply Fixes in Lovable
Rafter categorizes findings by severity (Critical, Warning, Improvement) and generates a fix prompt for each one. Copy the fix prompt, paste it into your Lovable AI chat, and the AI applies the fix directly.
Example Rafter fix prompt:
"Enable Row-Level Security on the user_profiles table.
Add a SELECT policy that restricts access to rows where
auth.uid() matches the user_id column. Add an UPDATE policy
with the same condition in both USING and WITH CHECK."
After applying fixes, push your changes and re-scan to confirm the vulnerabilities are resolved.
Step 4: Automate Future Scans
Connect Rafter to GitHub Actions so every push triggers a scan automatically. This catches new vulnerabilities as soon as they're introduced — before they reach production.
See our GitHub Actions integration guide for setup instructions.
A Security Checklist for Every Lovable App
Run through this before every deployment:
Database Security
- RLS is enabled on every public table (
SELECT tablename FROM pg_tables WHERE schemaname = 'public' AND NOT rowsecurity;returns zero rows) - Every RLS policy uses a real condition — no
USING (true)on sensitive tables -
WITH CHECKis set on INSERT and UPDATE policies - The
service_rolekey appears only in server-side code (Edge Functions, API routes) - The
anonkey is the only Supabase key in client-side code
Secrets
- No API keys, tokens, or passwords in source files
- All secrets are in environment variables
-
.envis in.gitignore - Any previously committed secrets have been rotated
Code Quality
- User input is validated before database operations
- User-generated content is sanitized before rendering
- Dependencies are up to date (run
npm audit) - No
eval(),dangerouslySetInnerHTML, or unescaped template literals with user data
Scanning
- Rafter scan shows zero Critical findings
- Automated scanning is configured for every push
Going Deeper
This guide covers the most common Lovable app vulnerabilities. For deeper dives into specific topics:
- Supabase RLS for Vibe-Coded Apps — comprehensive RLS patterns for every table type
- How to Run a 5-Minute Security Audit on Your Lovable App — step-by-step Rafter walkthrough with screenshots
- Vibe Coding Security: The Complete Guide — full attack surface coverage across all AI builders
- Next.js Security Checklist for AI-Generated Projects — framework-specific hardening
Conclusion
Lovable makes building apps fast. That speed is valuable — but it shifts the security burden entirely to you. The 10.3% critical RLS flaw rate isn't a platform failure. It's a gap in the workflow between generating code and deploying it.
The fix is straightforward: understand the five most common vulnerabilities, run a Rafter scan before you deploy, and use the checklist above on every release. Your Lovable app can be both fast to build and safe to ship.
Start scanning now — rafter.so