Security by tool
Supabase: is your database readable without signing in?
Supabase is built so that your frontend talks to the database directly. The anon key it needs is public — that is intentional. Access is not secured by the key but by Row Level Security: rules in the database that decide, row by row, who may see what. Without those rules, anyone who opens your site can download the entire table. This is the most common serious mistake in AI-built apps, and it can be checked from the outside.
The scan is free and takes about twenty seconds. You see straight away how many findings there are and how serious they are.
Scan your own app for freeWhy the public key is fine — and when it is not
Supabase has two keys. The `anon` key belongs in the frontend; it identifies your project, not a user. The `service_role` key bypasses every rule and belongs on a server only.
So the anon key is not secret and does not need to be. It only becomes dangerous when no rule checks what the asker is allowed to see. At that point it stops being an ID card and becomes a master key.
A `service_role` key in the frontend, by contrast, is always a critical finding — it overrides every rule.
Check it yourself in two minutes
Open your app, press F12, switch to “Network” and reload. Filter for `supabase`. You will see requests to an address of the form `https://<project>.supabase.co/rest/v1/<table>`.
Copy one of them including the `apikey` value and open it in a private window where you are not signed in. If rows come back, the table is readable without authentication.
Being signed out is the important part. In your own signed-in browser everything looks correct — which is exactly why this mistake goes unnoticed for so long.
Writing the rules correctly
Two steps, and they belong together. `ENABLE ROW LEVEL SECURITY` blocks everything at first — including your own app. Only the policies open it up again, deliberately.
Do only the first step and you have a working lock and a broken app. Do only the second and you have nothing: without RLS enabled, policies are never evaluated.
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY; -- Read: own row only CREATE POLICY "Read own profile" ON profiles FOR SELECT USING (auth.uid() = id); -- Update: own row only CREATE POLICY "Update own profile" ON profiles FOR UPDATE USING (auth.uid() = id) WITH CHECK (auth.uid() = id); -- Insert: only with your own id CREATE POLICY "Insert own profile" ON profiles FOR INSERT WITH CHECK (auth.uid() = id);
The three most common policy mistakes
First: `USING (true)`. A rule that permits everything — often set as a stopgap to get moving, then forgotten. To the database it is the same as no rule at all.
Second: `USING` without `WITH CHECK` on `UPDATE`. `USING` decides which rows someone may change; `WITH CHECK` decides what they may look like afterwards. Without `WITH CHECK`, someone can edit their own row so that it belongs to somebody else.
Third: rules on one table only. RLS applies per table. A protected `profiles` table helps little if `orders` holds the same personal data and is wide open.
Verify afterwards
Repeat the check above in a signed-out window. Now you should get an empty list or an error — not your data.
Then click through the app while signed in: does a user still see their own data? Rules that are too strict otherwise surface with your first customer.
What we check automatically
- Row Level Security per table (deep scan)
- service_role key in the frontend
- Reachable REST endpoints without authentication
Frequently asked questions
- Is the Supabase anon key secret?
- No, and it does not need to be. It identifies your project to Supabase, not a user. Protection comes from Row Level Security in the database. The `service_role` key, by contrast, is secret and must never reach the frontend.
- What does “Row Level Security is not enabled” mean?
- That the database does not check who is asking and simply returns the rows. Combined with the public anon key from your frontend, that means any visitor can download the full table.
- Do I need separate rules for every table?
- Yes. Row Level Security applies per table, and the policies have to match each table’s structure. A protected users table does not help if the orders table next to it is open.
- Is an open Supabase table a reportable data breach?
- If it holds personal data and was genuinely publicly retrievable, then under Art. 33 GDPR it is usually a reportable incident — with a 72-hour deadline towards the supervisory authority. This is not legal advice; in a real case it belongs in front of a lawyer.