Is your Lovable or Supabase app readable without a login? A 15-minute check
Researchers scanned 159,041 Lovable projects and found passports, payment details and 200,000 chat messages open to anyone. Why it happens, how to test your own Supabase app in 15 minutes, and the exact SQL that closes it.
Vishal Solanki · Full-stack developer, Noida
In November 2026, the Hamburg security firm Crissy Field published a study of apps built with Lovable, the AI app builder. Working with the German newspaper Handelsblatt, they looked at 159,041 Lovable projects and found personal and confidential data that anyone could read without logging in: passport numbers, payment details, private chat messages, contracts and invoices.
If you built an app with Lovable, Bolt, v0, Cursor or any other AI tool and it stores data in Supabase, this is worth 15 minutes today. The same mistake shows up in hand-written apps too.
Short version: your Supabase key is visible in the browser on purpose. The only thing protecting your data is Row Level Security (RLS) and grants inside the database. Open the SQL Editor, list the tables that have RLS switched off, then request one of your own tables with nothing but the public key. If rows come back, strangers can read them too.
Last checked: 15 November 2026.
What the researchers found
The numbers, as the report states them:
| Step | Count |
|---|---|
| Lovable projects examined (from the Common Crawl web archive, December 2025 to September 2026) | 159,041 |
| Of those, projects using Supabase | 34,545 |
| Sites that sent database records as soon as the page loaded, no login | 24,664 |
| Of those, sites where further read-only requests returned tables containing email addresses | 7,403 (30%) |
The researchers are careful about that last figure. An email address can be a public contact address or test data, so 30% is not a confirmed leak rate, and their sample doesn't represent every Lovable app. Some news coverage quotes 24,664 or 7,403 as "the number of leaking apps". Neither is quite what the report says.
What they did confirm is serious enough:
- A lifestyle forum exposed more than 80,000 profiles with names, emails and dates of birth, plus more than 200,000 chat messages.
- A service provider exposed more than 1,000 records with passport numbers, expiry dates and nationalities.
- Event organisers and restaurants exposed tens of thousands of ticket and food orders, including phone numbers, addresses and payment information.
- A property manager exposed AI-written summaries of internal emails about tenants and owners.
The report could not establish whether any of this data had been misused.
Why this happens
A Supabase app talks to its database straight from the browser. To do that, the browser needs a key, called the publishable key (older projects call it the anon key). That key is meant to be public. Anyone can open your site, press F12 and copy it.
So the key is not what protects your data. Two settings inside the database do:
- Grants decide whether a role may read or write a table at all.
- RLS policies decide which rows that role may touch.
Supabase's own documentation puts it plainly: a table in an exposed schema without RLS is readable and writable by any role that has a grant on it. On many existing projects, new tables in the public schema are granted to every role by default, including anon, the role used for visitors who aren't signed in.
Put together: if a table has RLS off, or has a policy that says "everyone may read everything", then your login screen is decoration. The data is one request away.
AI builders produce an app that works. Whether signed-out strangers can read the table behind it is not something you see while clicking through your own app, logged in as yourself.
The 15-minute check
Do this only on your own project.
1. List tables with RLS switched off
In the Supabase Dashboard, open SQL Editor and run:
select tablename, rowsecurity
from pg_tables
where schemaname = 'public'
order by rowsecurity, tablename;
Every row showing false is a table with no row protection at all.
2. Look for policies that allow everything
select tablename, policyname, cmd, roles, qual, with_check
from pg_policies
where schemaname = 'public'
order by tablename;
Read the qual column. A value of true on a policy whose roles include anon or public means every visitor can read every row. That's right for a public product list and wrong for users, orders, messages or uploads.
3. Test it the way a stranger would
This is the check that matters, because it shows what actually happens rather than what the settings suggest. Use your project URL, a table name and the publishable key (it starts with sb_publishable_) from Settings → API Keys in the dashboard, or from the project's Connect dialog:
curl "https://YOUR-PROJECT.supabase.co/rest/v1/YOUR_TABLE?select=*&limit=1" \
-H "apikey: YOUR_PUBLISHABLE_KEY"
If your project only has the older anon key (a long string starting with eyJ), send it in both headers:
curl "https://YOUR-PROJECT.supabase.co/rest/v1/YOUR_TABLE?select=*&limit=1" \
-H "apikey: YOUR_ANON_KEY" \
-H "Authorization: Bearer YOUR_ANON_KEY"
- An empty list
[]or a permission error: signed-out visitors can't read that table. Make sure the table has at least one row, or an empty list tells you nothing. - A row of real data: anyone on the internet can read it.
Repeat for every table that holds anything private. Supabase is retiring the anon and service_role keys at the end of 2026, so this is also a good moment to move to the new keys.
4. Check that users can't read each other's data
Being logged in is not the same as being allowed. Create two test accounts, add a record as the first, then sign in as the second and try to open it. If the second account can see it, your policy checks "is logged in" when it should check "owns this row".
5. Check storage buckets
In Storage, any bucket marked Public serves its files to anyone who has the link. Invoices, ID scans and private photos belong in a private bucket with policies.
6. Make sure the secret key never reached the browser
The secret key (older projects: the service_role key) bypasses RLS completely. It must only exist on a server. Check your built front end for both kinds.
New secret keys are plain text, so a search finds them:
grep -rE "sb_secret_" dist/ .next/static/ 2>/dev/null
The older service_role key is a JWT, so the words "service_role" don't appear in your files; they're encoded inside the key. List every JWT in the build instead:
grep -rhoE "eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+" dist/ .next/static/ 2>/dev/null | sort -u
Then decode each one on your own machine (don't paste keys into a website):
node -e "console.log(Buffer.from(process.argv[1].split('.')[1], 'base64url').toString())" "PASTE_KEY_HERE"
Seeing "role":"anon" is expected. Seeing "role":"service_role" means the master key is public. Rotate it in the dashboard straight away. Supabase rejects the new secret keys when they're sent from a browser, but anyone who copies one can still use it from other tools, so treat any match the same way.
7. Don't forget views
A database view skips RLS by default, so a view over a protected table can hand out every row the policies were meant to hide. On Postgres 15 and above, make it respect the caller's permissions:
alter view public.my_view set (security_invoker = true);
How to fix a table
For each private table, three things in one go: switch RLS on, take back the default grants, then give back only what the app needs. This follows the procedure in Supabase's RLS guide.
-- 1. Switch RLS on. With no policies, the public key now gets nothing.
alter table public.orders enable row level security;
-- 2. Remove the default access for visitors and signed-in users.
revoke all on table public.orders from anon, authenticated;
-- 3. Give signed-in users only the operations the app uses.
grant select, insert on table public.orders to authenticated;
-- 4. Limit those operations to the user's own rows.
create policy "Users read their own orders"
on public.orders for select
to authenticated
using ( (select auth.uid()) = user_id );
create policy "Users create their own orders"
on public.orders for insert
to authenticated
with check ( (select auth.uid()) = user_id );
Then run the curl test from step 3 again. Don't treat it as fixed until the stranger's request comes back empty and your app still works for a real user.
If you ask your AI builder to "fix the security", do the same: run the test yourself afterwards. A changed policy is not proof. An empty response is.
If your data was exposed
- Fix the access first, using the steps above.
- Changing the publishable key doesn't help. It was never a secret. Only the database rules matter.
- Look at your logs. Open Logs in the dashboard, pick the API Gateway log type and look for bulk reads of the affected tables from addresses that aren't yours. How far back you can see depends on your plan.
- Assume it may have been copied. Automated scanners look for exactly this pattern. The report notes that because these apps are built the same way, a method that works on one tends to work on many.
- Check your legal duties. If you hold personal data of people in the EU or UK, a breach that puts them at risk generally has to be reported to the data protection authority within 72 hours of you learning about it. Talk to a lawyer; I'm a developer, not one.
FAQ
I used Lovable's built-in database, not Supabase. Am I safe?
Unknown. The study only examined Supabase and says similar mistakes may exist with Lovable's own database features, which it did not test. Run the same idea of test: request your data while signed out.
My app has a login page. Isn't that enough?
No. The login page lives in your front end. The database is reachable directly with the public key, so the rules have to live in the database.
Is this Lovable's fault or mine?
The report points out that Lovable's terms put responsibility for the app and its data on the customer. Whoever is to blame, the people whose data is in your tables will look to you.
Is Supabase insecure?
No. RLS is a standard Postgres feature and works well when it's switched on and tested. The problem is apps shipped without it.
Can a scanner tool check this for me?
Supabase's Security Advisor in the dashboard flags tables without RLS and is worth running. It can't tell you whether a policy matches what your business intends, which is why the two-account test in step 4 matters.
Sources
- Crissy Field: Lovable, many apps built on the platform expose sensitive data (opens in a new tab)
- Supabase docs: Row Level Security (opens in a new tab)
- Hacker News discussion of the report (opens in a new tab)
Built something with an AI tool and not sure who can read its data? I can review the access rules, fix them and show you the before and after. Get in touch.