Lock down your Supabase tables
Turn on Row Level Security and write policies so the public anon key can't read or change everyone's data.
Skip to the promptWho needs this
- Your app uses Supabase (including Lovable projects with Supabase or Lovable Cloud) and talks to the database from the browser.
Why it matters
- The Supabase anon key ships in your site's JavaScript by design. Row Level Security (RLS) is what stops that key reading every row. If RLS is off on a table, anyone can query it from their own browser.
- Privacy laws require appropriate security for personal data (for example GDPR Art. 32), and a breach can trigger notification deadlines: 72 hours to the regulator under the GDPR.
- Open tables are among the most common serious problems in apps built quickly with AI tools.
Before you paste
- Access to the Supabase dashboard (Table Editor and the Security Advisor).
- A list of which tables should be private to each user, which are shared within a team, and which are public.
The prompt
Copy it into your AI builder. Fill in anything in [brackets] first, and review every change before you ship it.
Audit and fix Supabase Row Level Security (RLS) in this project. Write all changes as a new migration file. 1. INVENTORY List every table in the public schema, whether RLS is enabled, and its current policies. Also list storage buckets and their policies, views, and database functions marked SECURITY DEFINER. 2. ENABLE RLS EVERYWHERE Enable RLS on every table in the public schema. A table with RLS on and no policies is closed to the anon and authenticated roles, which is the safe default. 3. WRITE THE NARROWEST POLICIES THE APP NEEDS Typical patterns: - Private to the owner: allow select, insert, update and delete where user_id = auth.uid(). On insert and update use WITH CHECK (user_id = auth.uid()) so nobody can write rows for someone else. - Shared within a team: allow access where the row's team_id is in the teams the user belongs to (use a membership table, not a column the user can edit). - Public read-only (for example published posts): a select policy for anon with a condition such as published = true; no write policies for anon. - Never use USING (true) or WITH CHECK (true) for insert, update or delete unless the table is meant to be publicly writable. - Columns users must not change themselves (role, plan, credits, is_admin) must not be writable through an update policy: move them to a table only the server writes, or check them in WITH CHECK. 4. STORAGE Add policies on storage.objects so users can only read and write files under their own folder (for example a path that starts with their user id), unless a bucket is meant to be public. 5. VIEWS AND FUNCTIONS - Views: make them respect RLS (security_invoker = true) or remove access for anon. - SECURITY DEFINER functions bypass RLS: check each one verifies the caller and sets a fixed search_path. 6. KEYS Find any use of the service-role key outside server-side code (Edge Functions, API routes) and move it there. Show me, per table, the policies before and after. When you're done: list every file you changed, explain each change in one line, and give me a step-by-step test for each numbered item above. Don't change anything unrelated.
Builder tips
Lovable
After the migration runs, open Supabase → Advisors → Security Advisor and ask Lovable to fix anything it still flags.
Claude Code
Ask it to write a small test script that signs in as two test users and tries to read each other's rows, and run it after the migration.
Cursor
Use Agent mode and @-mention the files that matter (your layout, auth and database files), so it reads them before editing.
Common mistakes
- Turning RLS on and then adding a policy with USING (true), which re-opens the table.
- Update policies without WITH CHECK, letting users move rows to another user or give themselves a higher plan.
- Forgetting storage buckets, views and SECURITY DEFINER functions.
- Using the service-role key in browser code 'just to make it work'.
Check it worked
- 1Supabase dashboard → Table Editor: every table shows RLS enabled. Advisors → Security Advisor shows no RLS warnings.
- 2From a terminal, with your project URL and anon key: curl 'https://YOUR-PROJECT.supabase.co/rest/v1/YOUR_TABLE?select=*' -H 'apikey: YOUR_ANON_KEY'. For private tables you should get an empty list, not rows.
- 3Sign in as test user A, open the browser console and query user B's rows with the Supabase client: nothing comes back.
- 4Try to update your own row's plan or role column from the console: refused.
- 5Build the app and search the output for service_role: nothing.
What a SafeLaunch scan adds
SafeLaunch reads your migrations and code for open tables, exposed keys and other security gaps, and explains which breach and security rules apply in each market.
One free fix in your inbox each week
A new copy-paste prompt for Lovable, Cursor or Claude Code every week, with the rule behind it and how to check it worked. No spam, unsubscribe in one click.
By signing up you agree to get this weekly email. We send it with Kit, and you can unsubscribe at any time. What we share with Kit is in our privacy notice.
Questions
Sources
General guidance for common problems, not legal advice. Whether a rule applies depends on where you operate, who your users are and the size of your business.