Bolt and Supabase security

Bolt wires your app to Supabase and ships a public key so the frontend can read and write data. That key is fine on its own. It becomes a problem when the tables behind it have no access rules, because then anyone (not just your users) can read everything. This page checks it on your live app.

Scan your Bolt + Supabase app

The scan reads only what your app already ships to every visitor. It never stores your row data.

What it means

Anyone can read this table without logging in. That can be deliberate, such as reference data or a public directory, or a missing access policy; only you can say which. xlogs read one row's column names and kept none of its values.

Why AI tools cause it

Supabase only limits who can read a table once you turn on Row Level Security (RLS) and write access rules for it. An AI tool can build a working app without doing that, and a table without RLS, or with a policy that lets everyone read, answers anonymous requests.

How xlogs checks it

xlogs finds your public Supabase URL and anon key in your app (the same ones your frontend uses), asks the database for its table list, reads one row from each listed table as an anonymous user, and flags any that return rows. Hosted Supabase refuses that list to public keys, so on a hosted project the result says the tables could not be checked. Read-only: it never writes or deletes.

How the Bolt and Supabase combination goes wrong

This is not a bug in either tool. Row Level Security is a setting you turn on per table, and a fast build skips it. Once it is skipped, the public key Bolt ships can read every table that lacks it.

The scan finds your public Supabase URL and key in your app, then asks the database, as an anonymous user, whether its tables return rows, and which ones. It reports the table and its columns as proof and never stores your row data.

How to fix it

The goal: Row Level Security is enabled on every table, and each table returns rows only to the users it is meant for: a private table answers no anonymous request, and a table that is public on purpose has a read policy that says so.

  1. Decide whether the affected spot is meant to be readable without a login
  2. If it is not, enable Row Level Security (RLS) on the affected spot and add a policy so only the users allowed to see a row can read it
  3. If it is, keep it public on purpose, and record that decision so a review does not reopen it
  4. Do not switch on a restrictive policy for a table your public pages read without a replacement read policy, or those pages stop working
  5. Re-check that an anonymous request returns what you decided it should
Paste this to your AI coding tool (Lovable, Cursor, Claude Code):
In my app (the spot the scan shows): anyone can read this table; confirm that is deliberate. Please make this true: Row Level Security is enabled on every table, and each table returns rows only to the users it is meant for: a private table answers no anonymous request, and a table that is public on purpose has a read policy that says so. Steps: Decide whether the spot the scan shows is meant to be readable without a login; If it is not, enable Row Level Security (RLS) on the spot the scan shows and add a policy so only the users allowed to see a row can read it; If it is, keep it public on purpose, and record that decision so a review does not reopen it; Do not switch on a restrictive policy for a table your public pages read without a replacement read policy, or those pages stop working; Re-check that an anonymous request returns what you decided it should. Then tell me exactly what you changed, and do not print any secret values back to me or write one into a commit message, a pull request description or a log.

Then verify: After you decide and deploy, xlogs repeats the same anonymous read of each table the database lists and reports what comes back: a table you made private should no longer return rows, and one you keep public still will. If the table list is refused, it says the tables could not be checked.

Common questions

Is it safe to expose the Supabase anon key in a Bolt app?

Yes, the anon key is meant to be public and ship in the browser. It is safe only when your tables have Row Level Security policies. Without RLS on a table, that public key can read it. The scan tells you which case you are in.

How do I check Row Level Security on my Bolt project?

Test it from the outside, the way anyone on the internet would: the scanner sends an anonymous read to each table your database lists. If rows come back, anyone with your public key can read that table, whether RLS is off or a policy allows it. If nothing comes back, the table is empty or a policy filtered the read, which is not proof that RLS is protecting it. Hosted Supabase refuses the table list to public keys, so on a hosted project the scan says it could not check.