Supabase row level security for Lovable and Cursor apps
The anon key is public. Row level security is the access control. A policy that returns true for every row is not a boundary.
Lovable and Cursor apps that use Supabase ship the project URL and the anon key to the browser. That is the documented client setup. The key selects the anon role. It is not a password for your tables, unless row level security is off or a policy allows the world.
What to turn on
Enable row level security on every table in the exposed schema, including tables that only hold test rows today. With RLS enabled and no policy for the anon or authenticated role, that role gets no rows. A policy is a SQL expression. USING (true) on a SELECT policy for anon means every anonymous visitor can read the table. WITH CHECK (true) on an INSERT policy means they can write rows too. Name the person in the expression, for example auth.uid() = user_id on rows that belong to the signed-in user.
The service_role key bypasses row level security. Keep it in a server environment variable. If it is in JavaScript the browser downloaded, rotate it. A separate note covers how to recognize that role on the JWT.
A read you can do yourself
Send a GET to a table with the anon key, ask for one row, and do not send a user JWT. HTTP 401 or 403 means the request was refused. HTTP 200 with a JSON array means the anon role could read. Fix that before customer data is in the table. FlowCheck's free scan does this only when it finds an anon key, and it does not write.