A lot of apps built with Lovable, Bolt or Cursor handle "upgrade to Pro" with one line in the browser:
await supabase . from ( " profiles " ). update ({ is_pro : true }). eq ( " id " , user . id );
Enter fullscreen mode
Exit fullscreen mode
If that line works for your code, it works for anyone. Open DevTools, run the same thing in the console, and the paid plan is theirs. Same for plan , credits , role or trial_ends_at .
Why RLS doesn't stop it
The usual policy on a profiles table is "users can update their own row":
create policy "Users can update own profile" on public . profiles for update using ( auth . uid () = id );
Enter fullscreen mode
Exit fullscreen mode
That policy decides which rows a user may change. It says nothing about which columns . If billing columns live on that row, the user can change those too.
Deleting the update call from your frontend doesn't fix it either.…