For SaaS founders, Technical decision makers, AI SaaS founders, Developers taking over AI-built apps · Intermediate · Commercial · Solves: Supabase permission denied after deploy, Empty queries in production, Fear that other users can read data, AI generated SQL with no policies
Key takeaways
- The Supabase publishable key is public by design. RLS is what makes that safe.
- Policies do not revoke table grants. You have to set both.
- service_role bypasses RLS. It must never ship in a browser bundle.
- If you have not tested as a second user and as anon, you do not know whether RLS works.
If you only remember one sentence about vibe-coded SaaS, make it this: the Supabase key in your frontend is supposed to be public. The database is not.
Supabase Row Level Security is Postgres authorization that runs inside the database. Every query through the API gets an implicit filter. If RLS is off, or the policies are USING (true), that public key is a remote control for the tables you exposed.
This is where AI-built products get dangerous. The builder will happily create a users table, an invoices table, and a working UI. It will not automatically give you a second-user test, a grant audit, or a policy that survives a view. Founders then 'fix' a permission error by pasting the service role into the client. The error disappears. So does the security model.
What RLS actually is
Postgres runs two checks. Grants decide whether a role can run select, insert, update, or delete on the table at all. Policies decide which rows that operation applies to. You need both.
Supabase maps requests to roles. anon is a signed-out visitor. authenticated is a signed-in user. service_role is a server admin role that bypasses RLS. Keep that last one off every browser bundle, every committed .env that Vite will inline, and every screenshot in a Notion doc.
Adding policies does not take leftover grants back. A table that still grants insert to anon still has an insert path.
That last point is the one AI tools miss. They emit a policy that looks serious and leave the default privileges in place. On many existing projects, a new public table starts with full privileges for anon, authenticated, and service_role. The policy is not the whole story.
The three ways this shows up
Empty data after deploy
RLS is on. There is no policy that matches the request. Or the request has no user, so auth.uid() is null. The API returns an empty array or a 42501 permission error. The SQL editor still shows rows because that connection is privileged.
This is the annoying version. It is also the version that means Postgres is doing its job. Do not disable RLS to make the demo work on a sales call.
It works and it leaks
RLS was never enabled. Or a policy is USING (true) for anon. Or a view was created as security definer and bypasses the table policies. The UI looks correct because your account can see your rows. So can everyone else with the publishable key and a curl command.
Supabase is direct about views: they bypass RLS by default because Postgres creates them as security definer. A view over a protected table can hand out every row the policies were meant to withhold. If your AI tool created a 'public profile view' for convenience, read that section of the RLS guide before you celebrate the dashboard.
The admin key in the client
Someone got tired of empty arrays and switched the browser to service_role. That role bypasses RLS. There is no policy clever enough to save you after that. Rotate the key, treat the database as exposed for the time it was live, and rebuild the client with the publishable key.
A public example, not a private anecdote
On 31 January 2026, Wiz Research published how they found Moltbook's Supabase project wide open. The founder had said publicly that he did not write a line of code. Wiz found the publishable key in a Next.js chunk, queried the REST API, and received agent API keys, owner emails, and private messages. RLS was not doing the job. They reported about 1.5 million API tokens and tens of thousands of emails. Write access on posts stayed open after the first fix. The team patched it in hours after disclosure.
I am not claiming I worked on that product. I am using it because it is documented, primary-sourced, and it is the same architecture thousands of vibe-coded SaaS apps copy: a public key, PostgREST, and a hope that nobody opens DevTools.
Lovable is not Moltbook. Lovable also tells you, in its own Supabase docs, that missing RLS is the most common way app data gets exposed. That is the vendor talking, not a competitor scare page.
How to know whether you are safe
Do not ask the builder 'did you add RLS?' Ask for evidence.
- In the Supabase dashboard, open each table in an exposed schema. RLS should be on. If it is off, stop inviting users.
- List policies. You want a policy per operation, not one blanket true. Personal data should compare auth.uid() to the owner column. Team data should compare a membership table, not a string the client sent.
- Check grants. If anon can insert into invoices, that is a product decision you should be able to say out loud. If you cannot, revoke it.
- Open an incognito window. Call the REST endpoint with only the publishable key. Private tables should return empty or 401, not other people's rows.
- Create a second user. Confirm they cannot read the first user's rows, files, or customer records.
- Search the repo for service_role and for the secret key prefix. Any hit in client code is an incident.
Supabase's recommended close is unglamorous: put allow and deny tests under supabase/tests and run supabase test db. Until that suite passes, you do not know whether the policies do what you intended. A chat log that says 'added RLS' is not a suite.
What I would do on a taken-over repo
I would not start by rewriting the React pages. I would start with the schema.
- Dump tables, views, functions, and which ones are in an exposed schema.
- Enable RLS where it is missing. Expect the app to break. That break is information.
- Restore access with the smallest policy that matches the product, plus WITH CHECK on writes so a user cannot insert a row they could not read.
- Fix views that bypass policies.
- Move any service-role usage behind a server function.
- Only then talk about features.
If the rest of the app is a Lovable or Bolt prototype that still has to take payments, pair this with why those apps break when real users show up. RLS is the sharpest single risk. It is not the only production surface.
The founder version
You do not need to become a Postgres specialist. You need a rule: nobody else can read this row, and I can prove it.
If you cannot prove it, you are not ready for strangers, and you are definitely not ready for cards. Hire someone who will argue with the AI about policies, not someone who will generate more pages on top of an open table.
That is commercially boring work. It is also the work that decides whether your launch is a product or an incident. If you want that review done by someone who will stay in the repo until the second-user test passes, send the project ref and I will tell you what is actually public.
Implementation table
| Fix | Problem | What to change | Metric | Tool |
|---|---|---|---|---|
| Enable RLS | Table in public with no RLS | alter table ... enable row level security in a migration | API without a policy returns no rows | Supabase SQL Editor |
| Fix grants | Policies exist, anon can still insert | Revoke unused privileges on anon and authenticated | 42501 on operations you did not intend | Postgres grants |
| Policy per operation | One USING (true) policy | Separate SELECT, INSERT, UPDATE, DELETE with WITH CHECK | Second user cannot read first user rows | auth.uid() |
| Keep service_role off the client | Permission errors 'fixed' by admin key | Server-only admin client, rotate if leaked | Key absent from the browser bundle | Supabase dashboard |
Sources & references
- Row Level SecuritySupabase
Primary reference for grants, policies, service_role, and views.
- Hacking Moltbook: AI social network reveals 1.5M API keysWiz Research
Public incident: vibe-coded Supabase app with RLS disabled. Disclosed 31 Jan 2026.
- Security best practices for Lovable appsLovable
RLS as the final layer when frontend or backend logic fails.
- Connect to SupabaseLovable
Missing RLS is the most common exposure path in Lovable plus Supabase projects.










