Short answer
The biggest risk in an AI-built Supabase app is a table without row-level security: anyone with your public key can read or change it. Before launch, turn on row-level security for every table, keep secret keys out of the browser, make storage buckets private unless they must be public, and enforce admin rights in the database, not by hiding buttons.
Why AI-built apps leak data
Supabase's anon key is meant to be public, and it ships inside your app. That is safe only because row-level security (RLS) decides which rows each user can see. AI tools often create tables quickly and forget the policies, which leaves the data open to anyone who opens the browser's developer tools.
1. Row-level security on every table
- Open the Security Advisor in the Supabase dashboard: it lists tables without RLS.
- Enable RLS on every table in the public schema, then add policies that use auth.uid() so users only reach their own rows.
- Test with two real accounts: sign in as user A and try to load user B’s data by changing an id in the request. It must fail.
alter table orders enable row level security;
create policy "Users read their own orders"
on orders for select
using (auth.uid() = user_id);2. Users must not be able to make themselves admin
A common mistake: a profiles table with a role column and a policy that lets users update their own row. Any user can then set role to admin from the browser. Keep roles in a table users cannot write to, or limit the update policy to safe columns, and check admin rights inside database functions or server code.
3. Secret keys stay on the server
- The service role key bypasses all security. It must never appear in frontend code or in any environment variable starting with VITE_ or NEXT_PUBLIC_.
- The same goes for OpenAI, Stripe secret and email-service keys: anything bundled into the frontend can be read by visitors. Move those calls into Supabase Edge Functions or server routes and store the keys as secrets.
- If a secret key was ever in the frontend or in a public repo, rotate it now. Removing it from the code does not remove it from people who already copied it.
4. Storage buckets
Public buckets are readable by anyone with the link. Use private buckets for user uploads, invoices and documents, add storage policies tied to the user, and serve files with short-lived signed URLs.
5. Payments
- Calculate prices on the server. Never trust an amount sent from the browser.
- Verify the Stripe webhook signature, and let only the webhook mark orders as paid.
- Keep test and live keys apart, and remove test products before launch.
6. Sign-in settings
- Set the Site URL and Redirect URLs to your real domains only.
- Decide whether email confirmation should be on for your audience, and turn on CAPTCHA if bots are signing up.
- Check the rate limits in Authentication settings so nobody can flood your sign-up or email sending.
7. Before you press publish
- Search the codebase for console.log statements that print tokens, keys or user data.
- Make sure error pages do not show stack traces or database errors to users.
- Confirm you have backups, and that you know how to restore one.