WHY EVERY LOVABLE PROJECT NEEDS SECURITY TESTING
Building with AI is incredibly fast, but are your Lovable projects secure? Here's everything you need to know about protecting your AI-generated applications.
SCAN YOUR LOVABLE APP NOW
Paste your Lovable URL — the same probe we use in Lovable security research, on your project.
The Hidden Risk in AI-Powered Development
Don’t get me wrong — Lovable is amazing. You can build entire applications in hours that would have taken weeks before. But here’s the thing nobody talks about: when you’re moving this fast, security often gets left behind.
Traditional security tools weren’t designed for AI-generated code. They miss the unique patterns and potential vulnerabilities that emerge when an AI is writing significant portions of your application. That’s exactly why we built the Lovable Security Scanner.
Generic CVE scanners look for known library issues. Lovable apps often fail with zero vulnerable dependencies and a fully open invoices table. The scanner has to understand Supabase, anon keys, Edge Functions, and the six flows below — not only npm audit.
What Makes Lovable Projects Different?
AI coding assistants like Lovable have revolutionized how we build web applications. But they also introduce unique security considerations that traditional scanners can’t catch:
- Pattern-based vulnerabilities: AI sometimes generates code patterns that work perfectly but contain subtle security flaws
- Integration blind spots: When AI connects different services and APIs, security gaps can emerge between components
- Rapid iteration risks: The speed of AI development can lead to security debt accumulating faster than manual review can catch
Real Security Issues We’ve Found in Lovable Projects
After scanning many Lovable applications, we’ve identified several common security patterns developers should watch for:
AUTHENTICATION BYPASSES
Incomplete authentication flows that allow unauthorized access to protected routes.
API KEY EXPOSURE
Sensitive credentials accidentally exposed in client-side code or public repositories.
DATA LEAKAGE
User data or internal information unintentionally exposed through API responses.
INPUT VALIDATION GAPS
Missing validation allowing malicious input to reach your backend.
The 6 Vibe Coding Flows That Break Most Often
“Vibe coding” — shipping a working app by prompting an AI and never reading half the generated code — has a predictable shape. Across 1,430+ Lovable scans, the same six flows account for the majority of criticals. If you only hardened these, you would eliminate most production-impacting bugs in AI-generated apps.
01 / SIGNUP + SESSION
Anon key used for signed-in calls. JWT stored in localStorage with no refresh. Password reset that emails to attacker-controlled addresses because the update endpoint trusts the client.
02 / FILE UPLOAD
Supabase Storage bucket set to public. No MIME validation. No per-user path prefix. Result: any user uploads into any other user's folder, or uploads executable content to an "images" bucket.
03 / PAID-FEATURE GATE
Stripe plan check lives only in the React component. The underlying `/api/generate` or table read has no `WHERE plan = 'pro'` clause. Every free user can hit every paid endpoint directly.
04 / ADMIN DASHBOARD
Route guarded by `if (user.email === 'me@x.com') showAdmin()`. The `admin_audit_logs` table has no RLS. Anyone who guesses the route or calls the REST endpoint is admin.
05 / EDGE FUNCTIONS
Service-role key hardcoded into a function callable from the browser. Or function trusts a `user_id` parameter instead of deriving it from the JWT. Full privilege escalation in one request.
06 / PROFILE UPDATE
UPDATE policy reads `USING (auth.uid() = user_id)` but has no `WITH CHECK`. Users can change their own `role` to `admin`, their `plan` to `enterprise`, or their `user_id` to someone else's. Test: try `PATCH /profiles?id=eq.me { role: "admin" }`.
Each of these flows produces the same symptom in our reports: a clean UI, a working app, and a critical finding on the endpoint sitting behind it. The scanner walks through all six automatically. For a deeper treatment of why these patterns emerge in AI-generated code, see Vibe Coding Security Checks and the beauty-blogger RLS baseline.
RLS Misconfigurations: The #1 Vulnerability
After scanning 1,430+ Lovable applications, one vulnerability stands out above everything else: missing or misconfigured Row Level Security (RLS) policies. This issue continues to spike across new Lovable projects.
Lovable uses Supabase as its database layer. Supabase exposes a public API endpoint anyone can call directly. Without RLS policies, nothing stops an attacker from reading every row in your database, modifying other users’ data, or deleting records entirely.
- Supabase defaults: New tables are created with RLS disabled by default. Lovable’s AI doesn’t always enable it.
- Complexity gap: Writing correct RLS policies requires understanding PostgreSQL policies, which AI-generated code frequently gets wrong.
- Silent failure: Everything works perfectly without RLS during development. The vulnerability only matters when real users are on the platform.
- Multiple tables: Each new table needs its own RLS policies. As projects grow, tables get missed.
The Four RLS Checks Every Table Needs
RLS isn’t a single switch. It’s a policy per operation, per table. Miss one and the attack surface stays wide open. Our scanner runs all four against every public table we discover:
| Operation | What the scanner sends | What passes | What fails |
|---|---|---|---|
| SELECT | anon GET /rest/v1/<table>?select=* |
401 or scoped rows |
Full table dump |
| INSERT | anon POST /rest/v1/<table> with probe row |
401 or policy rejection |
Row written |
| UPDATE | anon PATCH /rest/v1/<table>?id=eq.<row> |
401 or zero rows affected |
Row modified |
| DELETE | anon DELETE /rest/v1/<table>?id=eq.<row> |
401 or zero rows affected |
Row deleted |
The common mistake: developers test SELECT, see it’s locked down, and ship. Writes stay wide open. We see this on ~30% of apps that “enabled RLS.”
Broken Policies That Look Correct
These are the RLS policies we flag most often — they compile, the app works, and the table is still exposed:
-- BAD: no WITH CHECK means user can INSERT rows owned by anyone
CREATE POLICY "insert own" ON posts
FOR INSERT USING (auth.uid() = user_id);
-- BAD: UPDATE with only USING; user can update their row
-- and simultaneously change user_id to someone else
CREATE POLICY "update own" ON posts
FOR UPDATE USING (auth.uid() = user_id);
-- BAD: reads your own + everyone marked public, but no column
-- filter — sensitive fields on "public" rows leak
CREATE POLICY "read public or own" ON posts
FOR SELECT USING (is_public OR auth.uid() = user_id);
-- BAD: the classic "make it work" fix
CREATE POLICY "allow all" ON posts FOR ALL USING (true);
The corrected versions — the patterns that actually hold under probing:
-- GOOD: INSERT with WITH CHECK binds the new row to the caller
CREATE POLICY "insert own" ON posts
FOR INSERT WITH CHECK (auth.uid() = user_id);
-- GOOD: UPDATE requires the row currently belongs to the caller
-- AND the new version still does (prevents ownership transfer)
CREATE POLICY "update own" ON posts
FOR UPDATE USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
-- GOOD: DELETE scoped to owner
CREATE POLICY "delete own" ON posts
FOR DELETE USING (auth.uid() = user_id);
-- GOOD: explicit SELECT, no ambiguity
CREATE POLICY "read own" ON posts
FOR SELECT USING (auth.uid() = user_id);
For PII columns (email, phone, stripe_customer_id), prefer a view with restricted columns over column-level grants — it’s harder to misconfigure and easier to audit.
Test Your RLS in 30 Seconds
You don’t need our scanner to do a first pass. Open your deployed Lovable app, grab the Supabase URL and anon key from the network tab, then:
# Replace with your project + anon key + table
SUPA="https://<project>.supabase.co"
KEY="<anon_key>"
TABLE="profiles"
# Read probe — should be empty or 401 when signed out
curl "$SUPA/rest/v1/$TABLE?select=*&limit=5" \
-H "apikey: $KEY" -H "Authorization: Bearer $KEY"
# Write probe — should be 401 or policy denial
curl -X POST "$SUPA/rest/v1/$TABLE" \
-H "apikey: $KEY" -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"id":"rls-probe-delete-me"}'
If rows come back or the write succeeds, you have a critical. Run the free Supabase RLS Checker for the full per-table report, or drop your URL into the scanner at the top of this page for the complete Lovable audit (auth, storage, edge functions, and keys in addition to RLS).
When RLS Is Not Enough
RLS stops the anon key. It does not stop:
- Edge Functions using the service-role key — these bypass RLS entirely. Any logic mistake inside a function is a full database read/write.
- Leaked JWTs — if a logged-in user’s JWT leaks (XSS, a public gist, a screenshot), RLS happily serves the attacker whatever that user could see.
- Storage — Storage has its own RLS-like rules per bucket and per object. They are configured separately and are usually left as “public bucket” on Lovable apps.
- Database views and functions — a
SECURITY DEFINERfunction runs with creator privileges, not the caller’s. If it takes a user-supplied ID and trusts it, RLS is irrelevant.
Our scanner probes all four. For background on the underlying policy model, see the Supabase RLS guide and the Supabase security checklist.
How the Lovable Security Scanner Works
Our scanner is specifically designed to understand Lovable’s architecture. Here’s what happens when you scan your project:
- Automated discovery: We crawl your application to understand its structure, routes, and functionality
- AI-powered testing: 13 specialized AI agents test different attack scenarios specific to web applications
- Vulnerability detection: We identify security issues from basic misconfigurations to complex authentication bypasses
- Actionable reports: Get clear explanations of issues found and specific steps to fix them
Beyond Just Scanning: Complete Security Coverage
The Lovable Security Scanner isn’t just about finding vulnerabilities. It’s a comprehensive security solution:
- Multi-browser testing: Ensure your app works securely across different browsers
- Supabase RLS verification: End-to-end testing of your Row Level Security policies
- Daily monitoring: Continuous scanning to catch new issues as your app evolves
- Data leak prevention: Detect sensitive information that might be exposed
- API token protection: Prevent accidental exposure of sensitive credentials
- Launch readiness checks: Comprehensive pre-deployment security validation
What Developers Are Finding
Across 5,711 vulnerabilities found in 1,430+ scanned apps, here’s how the most common issues rank:
- Missing RLS on SELECT — Critical. Present in 85% of scans.
- Missing RLS on INSERT/UPDATE/DELETE — Critical. Present in 51% of scans. Usually ships alongside a working SELECT policy, which is why devs miss it.
- Exposed API keys — Critical. Present in 62% of scans. OpenAI, Anthropic, Stripe, and Resend are the top four leaked.
- Authentication bypasses — High. Present in 45% of scans. Usually a frontend-only route guard.
- Missing input validation — High. Present in 38% of scans.
- Public Storage bucket — High. Present in 34% of Lovable apps using Storage.
- Frontend-only paid-feature gates — High. Present in 27% of monetized apps.
- Service-role key in client code — Critical. Rare (~4%) but always a full takeover.
How findings map to fix prompts
A scan only helps if the team can patch. Typical critical → action mapping for Lovable:
| Finding class | First fix move | Verify |
|---|---|---|
| RLS off on table T | ALTER TABLE T ENABLE ROW LEVEL SECURITY + owner policies |
Anon select empty; user B cannot read A |
USING (true) policy |
Drop; replace with auth.uid() = user_id |
Same probes |
| Service role in bundle | Remove; rotate key; use Edge Function | Token leak checker clean |
| Public Storage | Private bucket + path policies | Anonymous list/download fails |
| Client-only admin | Server role claim + RLS | Direct REST to admin tables fails |
| Edge Function no JWT | Enable verify; derive user from token | Unauthed invoke 401 |
| Paid gate UI-only | Check plan server-side / in policy | Free user API 402/403 |
Paste structured findings back into Lovable chat carefully: ask for minimal policy diffs, then rescan. AI “fixes” that add USING (true) to silence errors are regressions — the scanner’s job is to catch that loop.
What the 13 agents prioritize on a Lovable target
Without turning this into a product whitepaper, the agent set roughly covers:
- Stack fingerprint — confirm Supabase / hosting class.
- Secret harvest — JWTs and third-party keys in JS.
- PostgREST surface — table discovery and per-op RLS.
- Auth flows — signup, session storage, reset paths.
- Storage — bucket publicity and path traversal-ish layouts.
- Functions — unauthenticated and confused-deputy invokes.
- BOLA — cross-user ids on REST and custom routes.
- Config / headers — CORS, security headers, source maps.
- Payment trust — client price, webhook-shaped endpoints.
- Input / XSS sinks — HTML rendering paths when exposed.
- Admin surfaces — route and table naming heuristics.
- Regression hotspots — tables added without policies.
- Evidence packaging — repro requests for the report.
Humans still own business-logic abuse that needs domain knowledge (e.g. “invoice paid implies unlock chapter 7”) — but the list above is where Lovable apps actually bleed.
Interpreting rates from 1,430+ apps
Percentages are not destiny for your app, but they set priors:
- If you have not touched RLS, assume SELECT is open until proven otherwise (base rate ~85% in our scans historically).
- If SELECT works and you “enabled RLS,” still test writes (~51% still open on write ops).
- If you integrated OpenAI/Stripe in a hurry, assume key in client until the bundle grep is clean (~62% key issues of some kind).
Use rates for prioritization in portfolios (agencies scanning many client Lovable apps): fix open RLS fleet-wide before polishing CSP.
Agency and multi-project workflow
Teams shipping many Lovable clients should:
- Run detector + full scan on every handoff URL.
- Keep a shared policy SQL template (owner CRUD + org membership).
- Block go-live on any Critical.
- Retest after client “just added one feature” prompts.
- Document Supabase project ref ↔ client mapping for IR.
The scanner becomes a release gate, not a one-off audit theater.
False positives and how we reduce them
Dynamic testing can flag:
- Intentionally public marketing tables
- Read-only reference data meant for anon
- Rate limits that look like auth failures
Mark accepted risks explicitly in your report process. Prefer public-by-design tables with clear names (public_posts) and policies that only expose non-PII columns — not silent USING (true) on users.
Continuous scanning vs one-shot
Lovable apps change when someone opens chat and says “add teams.” That prompt can create three tables with RLS off. One launch scan is necessary; weekly or per-deploy scanning is what matches the generator’s cadence. Pair with CI/CD security when you export to GitHub.
How to read a Lovable scan report
Prioritize: anonymous data access, service_role exposure, BOLA, public storage with sensitive objects, then headers and hygiene. Fix prompts should be applied one critical at a time with a rescan between to avoid masking regressions.
Cadence recommendation
Scan on every production deploy and after any prompt that mentions database, auth, storage, or payments. Weekly full scans catch drift from dashboard clicks outside git.
Dual-user BOLA walkthrough the scanner automates
After RLS looks “enabled,” most teams still miss object-level failures. The scanner’s dual-user path roughly does this:
- Create or reuse two test identities (or reuse sessions you provide).
- As user A, create a resource (invoice, post, project) via the same REST surface the SPA uses.
- As user B, request that resource by ID:
GET /rest/v1/<table>?id=eq.<A's id>with B’s JWT. - Attempt
PATCHandDELETEas B against A’s row. - Flag any 200 that returns A’s PII or mutates A’s row.
You can reproduce the same check manually in five minutes:
# After signup as A and B, capture access tokens from the network tab
# A creates a row; then:
curl -s "$SUPA/rest/v1/invoices?id=eq.$A_INVOICE_ID&select=*" \
-H "apikey: $ANON" -H "Authorization: Bearer $B_JWT"
# Expect: empty array or 401/403 — never A's line items and amounts
If B sees A’s invoices, you have BOLA even when a naive “SELECT works for me” test passed. Pair with Lovable BOLA for the disclosure context.
Storage path prefixes that look private but are not
Lovable upload flows often write to paths like avatars/{user_id}/photo.jpg while the bucket is public. Path structure is not access control. Attackers enumerate UUIDs or scrape listing endpoints.
Scanner checks that matter:
- Bucket public vs private flag
- Ability to
listanother user’s prefix with the anon key - Ability to download by constructed URL without a signed token
- MIME and size — less critical than confidentiality, still abuse surface
Fix pattern:
-- storage.objects policies (bucket private)
create policy "read own avatar objects"
on storage.objects for select
using (
bucket_id = 'avatars'
and auth.uid()::text = (storage.foldername(name))[1]
);
create policy "insert own avatar objects"
on storage.objects for insert
with check (
bucket_id = 'avatars'
and auth.uid()::text = (storage.foldername(name))[1]
);
Then regenerate filenames server-side (UUID) so enumeration of original upload names fails even if a policy slips.
Edge Function probe matrix
For every discovered /functions/v1/<name>:
| Probe | Expected if hardened |
|---|---|
No Authorization header |
401 |
| Valid user JWT, wrong resource id in body | 403/404, not other users’ data |
Body field user_id overridden to another UUID |
Still scoped to JWT subject |
| OPTIONS / CORS preflight from random origin | No * with credentials |
Functions that embed service_role and trust body fields are full-database gadgets. Prefer user-scoped clients:
const supabase = createClient(url, anonKey, {
global: { headers: { Authorization: req.headers.get("Authorization")! } },
});
const { data: { user } } = await supabase.auth.getUser();
if (!user) return new Response("Unauthorized", { status: 401 });
Use service role only for admin jobs with fixed queries and no client-supplied table/path names.
Anon key extraction is reconnaissance, not the vuln
Finding eyJ... JWTs labeled anon in the Vite bundle is expected for Supabase. The report should not scare teams into “hiding” the anon key in localStorage gymnastics. What matters:
- Which tables respond to that key with rows
- Whether the key is actually a service_role JWT mislabeled (rare but Critical)
- Whether third-party secrets (
sk_live_, OpenAIsk-) ride along in the same chunks
The scanner separates “anon present” (info) from “anon can dump profiles” (Critical). Educate stakeholders with that split so they fix policies instead of cargo-culting key obfuscation.
Prompt-induced regressions the scanner is built to catch
Typical chat after first launch:
“Add a teams feature with shared invoices”
Generator adds teams, team_members, invoices.team_id — often without RLS or with USING (true) so the UI works. Next:
“Let anyone with the link view the invoice”
Public read policy lands on the whole table, not a tokenized share row.
Operational rule: any prompt that mentions database, auth, storage, payments, admin, or share links triggers a rescan before the marketing tweet. The six flows and RLS four-ops matrix above are the regression suite.
Lovable Cloud vs export-to-GitHub: same probes
Whether the app lives on *.lovable.app or was exported to Vercel, PostgREST and Storage still answer on *.supabase.co. Hosting change does not retire the scanner. After export:
- Confirm env mapping did not promote service role to
VITE_/NEXT_PUBLIC_. - Re-run token leak on the new bundle (chunking differs).
- Re-check Auth redirect URLs for the new domain.
- Keep the same dual-user and anon probes.
See Lovable tech stack for the architecture map and CI/CD security guide for preview gates.
Report severity vs business severity
A Medium “missing CSP” on a marketing landing page is not the same as a Medium “partial RLS — writes open on waitlist.” For Lovable SaaS with invoices:
| Finding | Business read |
|---|---|
Anon SELECT on invoices |
Stop ship / incident |
| Public Storage with contracts | Stop ship |
| Service role in bundle | Stop ship + rotate + audit logs |
| Missing HSTS | Fix this sprint |
| Verbose PostgREST error | Fix this sprint |
Use the appendix rubric, but always re-rank by data class in the table (PII, payments, health). The scanner ranks technically; you rank for customers and regulators.
Paste-ready Lovable prompts after a scan
Use one prompt per critical. Rescan between prompts.
RLS baseline:
Enable RLS on every public table. For each user-owned table, add policies:
SELECT/UPDATE/DELETE using auth.uid() = user_id;
INSERT with check auth.uid() = user_id;
UPDATE also with check auth.uid() = user_id.
Do not use USING (true). Do not put service_role in the client.
Storage:
Make the avatars bucket private. Add storage.objects policies so users can only read/write objects under a folder named with their auth.uid(). Reject non-image MIME types over 5MB.
Edge Function:
Require a valid user JWT on this function. Create the Supabase client with the caller's Authorization header and anon key so RLS applies. Remove service_role from this function unless you justify a fixed admin query with no client-supplied table names.
Bad follow-ups to avoid: “just make the error go away,” “allow all authenticated users,” “disable RLS temporarily.”
What “1,430+ apps” means methodologically
The corpus is live public URLs scanned with the same probe family over time — not a random sample of all Lovable projects ever created. Selection bias exists (public deploys, researchers and founders who paste URLs). Rates are priors for prioritization, not guarantees for your private staging app.
Still, the rank order of issues (RLS → keys → auth bypass → storage) has been stable across corpus refreshes. Use it to order remediations when you cannot fix everything in one day.
Related research and deeper dives
- Lovable Security Report Feb 2026 — mass open databases
- Lovable BOLA — object-level auth failures
- Is Lovable Safe? — platform vs app gap model
- Supabase safety — RLS deep context
- Vibe hacking — how attackers industrialize the same probes
Getting Started Is Simple
You don’t need to be a security expert to protect your Lovable projects. Just paste your deployed app URL above. In minutes, you’ll have a comprehensive security report with actionable recommendations.
Start with a 14-day free trial. No lengthy setup. Just real security insights for real applications. The difference between a fun demo and a product you can defend is usually a few policies, a rotated key, and a scanner in the loop — not a six-month rewrite.
Appendix: severity rubric we use on Lovable apps
| Severity | Examples |
|---|---|
| Critical | Anon readable user PII; service_role in client; auth bypass to admin data |
| High | Authenticated BOLA; public Storage with private files; unsigned billing webhook |
| Medium | Verbose errors; missing rate limits; partial RLS (writes open) |
| Low | Missing security headers; informative server banners |
Use the rubric in tickets so “we’ll fix mediums later” does not swallow a High write-path gap mislabeled by accident.
COMMON QUESTIONS
RUN THE LOVABLE SECURITY SCAN
Same failure modes we publish about: missing RLS, exposed keys, broken auth. Get the report for your app in under 60 seconds.
14-day free trial · No credit card · Cancel anytime