AI-coded apps

    Lovable.dev Supabase RLS bypass: how it happens, how to fix it

    Lovable.dev Supabase Row Level Security bypass vulnerability

    If you found this page after seeing a security advisory about your Lovable.dev app, or after a friend pointed out that your Supabase tables read straight from a browser, this is the page that walks you through the fix in order. The vulnerability has a CVE, a documented attack vector, and a small set of policies that close it.

    Short answer

    The bypass works because Lovable's AI creates Supabase tables without consistently enabling Row Level Security, and the public anon_key shipped in the client lets anyone query those tables directly. According to Matt Palmer's CVE-2025-48757 disclosure published on May 29, 2025, 303 endpoints across 170 Lovable projects were exposed at the time of publication. Fix it by enabling RLS on every table and replacing any USING (true) policy with auth-scoped checks.

    What you should know

    • CVE-2025-48757 documents the exact attack pattern, published after a 45-day coordinated disclosure window that closed without a meaningful Lovable fix.
    • The anon_key is public by design. Rotating it does not help; the missing piece is the RLS policy.
    • Lovable's 2.0 security scan checks policy existence, not effectiveness. A USING (true) policy passes the scan while leaving the table fully open.
    • An app that ships secure can regress. Lovable's AI adds new tables as features ship and does not always carry forward the policy pattern from existing tables.
    • The attack works without authentication. An attacker opens devtools, copies the anon_key, and runs queries against *.supabase.co directly.

    How does the RLS bypass actually work?

    Supabase's API layer (PostgREST) translates HTTP requests into SQL queries, executed under one of two roles: anon for unauthenticated traffic and authenticated for logged-in users. According to Supabase's official documentation on Row Level Security, RLS policies act "like adding a WHERE clause to every query." When a table has no policy, or has a USING (true) policy, the WHERE clause is effectively absent and every row is returned.

    The Lovable bundle ships the project URL and the anon_key as static strings in the JavaScript that any visitor downloads. Both are meant to be public, paired with proper RLS policies. The CVE entry shows the exploit reduces to three lines: open devtools, read the values from the network panel, then fetch https://<project>.supabase.co/rest/v1/<table>?select=* with the anon_key in the apikey and Authorization headers. No login required.

    Lawrence has seen this pattern repeat across audits of vibe-coded apps that started as a single-table prototype and grew. The first table got a policy. The fifth one, added six weeks later by a different AI prompt, did not.

    What did CVE-2025-48757 actually expose?

    The data inventory across the 170 documented projects is broad. The Superblocks technical writeup of the CVE lists emails, usernames, phone numbers, payment statuses, API keys for Gemini and Google Maps, developer credentials, home addresses, personal debt amounts, and Stripe integration endpoints that accepted payment parameter injection.

    A February 2026 follow-up audit by VibeEval scanned 1,645 Lovable apps and found that more than 170 still had fully exposed databases. One showcased EdTech app exposed 18,697 user records, with authentication logic that, in the auditor's words, "blocked logged-in users and let anonymous ones through."

    The specific exploit was not novel. PostgREST has documented how query parameters work since launch. What was new was the volume: 170 production apps shipped by Lovable users who trusted the platform's defaults more than their security team would have.

    How do you find the broken tables in your own app?

    Four checks, in order of confidence:

    1. Open the Supabase dashboard, go to Authentication, then Policies. Every table that holds user data should appear with one or more policy rows. Tables marked as "RLS disabled" are public.
    2. For each policy, open the body. Anything that reads USING (true) is functionally equivalent to no policy at all for the operation it covers (SELECT, INSERT, UPDATE, or DELETE).
    3. From a browser in incognito mode (no logged-in session), open devtools, copy the anon_key from your bundle, and try to query each sensitive table. If you get rows back, the policy is broken.
    4. Run Lovable's built-in security scan as a baseline, but do not trust it. The scan tells you which tables are obviously exposed; it does not tell you which policies are permissive in practice.

    For mobile apps built on top of a Lovable backend, PTKD.com (https://ptkd.com) is one of the scanners that maps Supabase endpoints out of a compiled APK or IPA and flags the ones that respond to unauthenticated queries. The report ties findings to the relevant OWASP MASVS controls and recommends specific policy patterns per table type.

    What does a correct RLS policy look like?

    The correct pattern depends on the access shape of the table. The three patterns that cover most Lovable schemas:

    Table typeCorrect patternWhy this and not the alternatives
    User-owned records (todos, notes, files)USING (auth.uid() = user_id)Each user only sees their own rows. The auth.uid() function returns the JWT-validated user id, not a client-provided value.
    Tenant-scoped records (multi-org apps)USING (org_id IN (SELECT org_id FROM memberships WHERE user_id = auth.uid()))Joins against a membership table so admins of an org can see all org rows but not other orgs.
    Public read, authenticated writeUSING (true) for SELECT, USING (auth.uid() IS NOT NULL) for INSERT/UPDATEPublic read is intentional for catalog or blog tables. Writes still need an authenticated session.

    The trap is the third row. A USING (true) policy on SELECT for a public catalog is correct; the same body on a user-data table is the vulnerability. The check is whether the table holds anything that should not appear in a search engine result.

    For INSERT and UPDATE on user-owned tables, add a WITH CHECK clause as well: WITH CHECK (auth.uid() = user_id). The USING clause filters the rows the query can read; WITH CHECK filters the rows the query is allowed to write. Without both, a user can read their own rows but update someone else's.

    What about Lovable's built-in security scan?

    Lovable released the security scan feature in version 2.0 on April 24, 2025, three weeks after Palmer's initial disclosure. The scan reports the number of tables with RLS enabled and flags tables without policies. Per the published CVE writeup, the scan does not evaluate whether the policies actually restrict access.

    In practice this means a table with ENABLE ROW LEVEL SECURITY and a single USING (true) policy gets a green check from the Lovable scanner while remaining fully readable from any browser. Treat the scan as a low-bar smoke test, not a security audit.

    What to watch out for

    Three details that get missed during the rush of a fresh disclosure:

    First, Supabase Auth tables (auth.users in particular) are not in your project schema and have their own access rules. Querying auth.users directly from the client returns nothing for the anon role even without RLS policies; the table is hidden behind the auth schema. The exposure happens when developers create a profiles table in the public schema and mirror auth data into it without an RLS policy.

    Second, the JWT aud claim is not the same as auth.uid(). Earlier versions of Supabase Auth issued JWTs with a generic aud of authenticated; some Lovable templates wrote policies that checked the audience instead of the user id, which let any logged-in user see every other logged-in user's data. The 2025 dev.to audit of 50 Lovable apps found this pattern in 28% of audited projects.

    Third, fixing the policies does not retroactively unbreach the data. If your access logs show queries from unexpected IPs in the weeks before the fix, treat the affected records as compromised and follow your standard incident-response steps. Supabase retains query logs for seven days on the free tier and longer on paid plans.

    Key takeaways

    • The Lovable.dev RLS bypass works because anon_key plus missing or permissive policies equals public data. Rotating the anon_key does not help.
    • USING (true) is the policy body to grep for. It is the AI's most common shortcut and the easiest one to leave in production.
    • Lovable's security scan checks existence, not effectiveness. Audit policy bodies by hand or with a third-party scanner.
    • For Lovable apps compiled to mobile, PTKD.com (https://ptkd.com) scans the APK or IPA and flags Supabase endpoints that return data without authentication.
    • Document the policy review in your CHANGELOG so the next AI-generated migration cannot quietly revert the fix.
    • #lovable
    • #supabase
    • #rls
    • #cve-2025-48757
    • #ai-coded apps
    • #row-level-security

    Frequently asked questions

    Is Lovable's built-in security scan enough to detect this?
    No. Lovable 2.0 added a security scan after the April 2025 disclosure, but the scan only checks whether an RLS policy exists, not whether the policy actually restricts access. A policy with USING (true) passes the check while leaving the table fully readable. You need to manually inspect each policy body or use an external RLS-effectiveness checker.
    Will rotating my anon_key fix the problem?
    No. The anon_key is meant to be public; rotation does not help when the underlying RLS policies are missing or permissive. Rotating the key only matters if you accidentally shipped the service_role key, which is a separate problem. The real fix is to write per-resource policies that scope by auth.uid() and tenant id.
    How do I tell if an attacker has already accessed my data?
    Open the Supabase dashboard, go to Logs & Analytics, and filter by your anon role. Look for query patterns that select large numbers of rows from sensitive tables, or for IP addresses outside your expected user base. Supabase retains query logs for seven days on the free tier and longer on paid plans.
    Should I take my Lovable app offline while I fix the policies?
    Only if the exposed tables contain regulated data (health, financial, identity). For most apps, enabling RLS on every table and writing correct policies takes under an hour, and the window of additional exposure during that hour is small compared to the existing leak. Schedule the work for off-peak hours and apply policies in a single migration.
    Is this vulnerability only a Lovable problem, or does it affect other AI builders?
    The same pattern appears in apps generated by Bolt, v0, Cursor with Supabase templates, and Replit Agent. Any AI builder that auto-generates Supabase tables without enforcing per-table RLS shows the same failure mode. The Lovable disclosure attracted attention because it was the first published CVE; the underlying risk is shared across the AI-coded app category.

    Keep reading

    Scan your app in minutes

    Upload an APK, AAB, or IPA. PTKD returns an OWASP-aligned report with copy-paste fixes.

    Try PTKD free