Security for AI-Built Apps: Supabase, Firebase and What a Scan Can’t See
AI app builders get a working product online in an afternoon. Most of them put the logic in the browser and connect it directly to a hosted backend such as Supabase or Firebase. That is a fine design, with one condition: the backend’s access rules must decide what each visitor may read and change, because the browser code can be read and edited by anyone.
An outside scan like ours sees the page, its headers and the keys it ships. It can’t log in, so it can’t see whether a stranger can read your users’ rows. Those checks are yours to run, and they take about an hour.
What we check from outside
- Which builder and backend the page uses, from its domain and the code it loads.
- Secret keys in the page: a Supabase
service_roleorsb_secret_key, Stripe or Clerk secret keys, AI provider keys (OpenAI, Anthropic, Groq, xAI, Hugging Face, Replicate). A secret key in the browser gives anyone full access, whatever your rules say. - Payments in test mode: a Stripe
pk_test_key on the live site. - Published source maps: your original code, readable by anyone.
What to check yourself
1. Database access rules
- Supabase: turn on Row Level Security for every table in the exposed schemas, then add policies. A table with RLS off is readable and writable by anyone holding the public anon key, and that key is in your page by design. Open the Security Advisor in the Supabase dashboard; it lists tables without RLS.
- Policies should compare the row’s owner with
auth.uid(). A policy likeusing (true)or “any signed-in user” lets every user read everyone’s data. - Users must not be able to update their own
role,planorcreditscolumns. Keep those in a table only the server writes, or limit updates to the columns users may change. - Firebase: rules such as
allow read, write: if true;orif request.auth != nullare open to the world or to every signed-up user. Match each document torequest.auth.uid, and test the rules in the Rules Playground or the emulator.
2. Storage buckets
Files users upload (invoices, ID photos, avatars) belong in private buckets with policies like the tables. Public buckets are for files everyone may see.
3. Keys and functions
- Only the anon or publishable key belongs in the browser. Service keys, payment secret keys and AI keys live in server-side functions (Supabase Edge Functions, Firebase Functions, your API).
- Database functions (
rpc) run with the rights they are defined with: check that each one checks who is calling it.
4. Payments and paid features
- Verify webhook signatures and ignore events you have already processed.
- Take prices from your server or the payment provider, never from the browser.
- Decide “is this user paying?” on the server for every paid feature, not by hiding buttons.
5. AI and other costly endpoints
An endpoint that calls an AI model with your key needs a login, a rate limit and a spending cap, or someone can run up your bill.
Test it like a stranger
Create a second account and, in the browser’s console, try to read and change the first account’s data with the same client library your app uses. Then try again signed out. Anything that works there works for everyone.