LeakPatrol › Guides

Can users give themselves your paid plan?

By LeakPatrol · Updated 25 October 2026

In a lot of Lovable, Bolt and Cursor apps, the "upgrade" step is a line like supabase.from("profiles").update({ is_pro: true }) running in the browser. If that works for your code, it works for anyone: open the console, run the same line, and the paid plan is theirs for free. The same goes for plan, credits, role and trial_ends_at.

Why it happens

The usual Row Level Security policy on a profiles table is "users can update their own row": using (auth.uid() = id). That policy decides which rows a user may change, not which columns. If billing columns live on that row, the user can change them too. Removing the update from your frontend code doesn't help on its own: the policy still allows it, and anyone can write the request by hand.

Test it on your own app (30 seconds)

  1. Log in to your app as a normal, free user.
  2. Open DevTools, Console, and run: await supabase.from("profiles").update({ is_pro: true }).eq("id", (await supabase.auth.getUser()).data.user.id) (use your own table and column names; if supabase isn't defined, find the client in your bundle or test with a REST call).
  3. Reload. If you're now pro, so is anyone who tries it.

Only run this on apps you own.

Fix 1: only let users update the columns they should

Postgres can grant updates per column. RLS still decides the rows; the grant decides the columns.

revoke update on table public.profiles from anon, authenticated;
grant update (full_name, avatar_url) on table public.profiles to authenticated;

List every column users are allowed to edit. If users insert their own profile row, do the same for insert, so nobody can sign up with is_pro = true.

Fix 2: block billing changes with a trigger

If you'd rather keep the table grants as they are, a trigger can refuse changes to billing columns unless they come from your server:

create or replace function public.protect_billing_columns()
returns trigger language plpgsql as $$
begin
  if (new.is_pro is distinct from old.is_pro or new.plan is distinct from old.plan)
     and coalesce(auth.role(), '') <> 'service_role' then
    raise exception 'billing columns can only be changed by the server';
  end if;
  return new;
end $$;

create trigger protect_billing before update on public.profiles
for each row execute function public.protect_billing_columns();

Fix 3 (cleanest): move billing to its own table

Keep subscriptions or credits in a separate table with a select policy for the owner and no insert, update or delete policies. Only your payment webhook writes to it, from an Edge Function using the service_role key (which stays a server secret).

Then let only the webhook set it

Your Stripe or Lemon Squeezy webhook should verify the signature, then set the plan with the service_role key on the server. Never set the plan from the "thank you" page in the browser: anyone can open that page.

A prompt that works in Lovable, Bolt or Cursor: "Users must not be able to change is_pro, plan or credits. Only the payment webhook, in an Edge Function that verifies the signature and uses the service role key, may set them. Remove all frontend code that writes these columns, revoke UPDATE on profiles from authenticated and grant it back only for the columns users may edit. Do not turn off RLS."

What the free scan checks

The LeakPatrol scan reads the JavaScript your live site loads and flags code that writes paid columns (is_pro, premium, plan, credits and similar) straight from the browser. It can't see your policies, so after fixing the code, run the console test above too.

Check your app in a minute

Free, read-only, no sign-up. Every problem comes with a plain-English fix and a prompt for your AI tool.

Get the 10-point checklist

Every check we run on AI-built apps, with how to do each one yourself. Straight to your inbox.

Questions

Can users change their own is_pro column in Supabase?

Yes, if the profiles table has an update policy like auth.uid() = id and is_pro is on that table. RLS limits which rows a user can update, not which columns.

How do I stop users updating a column in Supabase?

Revoke UPDATE on the table from the authenticated role and grant it back only for the columns users may edit, for example grant update (full_name, avatar_url) on public.profiles to authenticated. Or use a trigger that rejects changes unless auth.role() is service_role.

Where should a subscription status be set?

Only in your payment webhook on the server, after verifying the webhook signature, using the service_role key. Never from the browser or the checkout success page.

Does removing the update from my frontend fix it?

No. Anyone can send the same request by hand with your public anon key and their own login. The database has to refuse it.

More guides