This is the checklist we work through when we take on production operations for an app built with Claude Code, Codex, Lovable, Replit, Cursor or similar, typically running on Vercel or Netlify with Supabase underneath. None of it requires rewriting the app. Most of it is settings.
1. The database, because that is where the customers are
In a Supabase app, the browser talks to the database directly through the Data API, using the publishable (anon) key that ships in the front end. That is the design, and it is safe for one reason: row level security decides which rows each request can reach.
Supabase's Table Editor switches row level security on when it creates a table. A table created with SQL or a migration starts with it off, and generated code tends to create tables with SQL. Supabase's Security Advisor, in the project dashboard, flags every table in the public schema without it, in these words: "Anyone with your project URL can read, edit, and delete all data in this table."
Row level security switched on with no policies blocks everything, which breaks the app. The quickest way to make the error go away is a policy of using (true), which for reads is the same as switching it off. The detail is in our piece on Supabase row level security mistakes.
2. Every key that ships to the browser
Front-end frameworks inline public environment variables into the JavaScript bundle at build time. In Vite, which a lot of AI-generated front ends use, that is anything prefixed VITE_. In Next.js it is NEXT_PUBLIC_. Both projects' documentation says the same thing: do not put secrets there.
The Supabase publishable key is meant to be public. The secret key, called the service role key in older projects, is not: it bypasses every row level security policy. If it has ever been in front-end code, treat it as leaked. Supabase's guidance is to create a replacement, move every service onto it, then delete the old one.
The same applies to payment provider secret keys and AI provider API keys. An AI API key in the browser is a bill that anyone can run up.
Deleting the key from the latest commit does not remove it from Git history. Rotation does.
3. Who can read the files
Supabase Storage has public and private buckets. Anything in a public bucket can be read by anyone who has the URL, which is exactly right for logos and product images and exactly wrong for invoices, uploaded documents and profile photos. Private buckets are controlled by policies on the storage.objects table, and they deserve the same review as the tables that hold the rest of the customer data.
4. Views and functions that step around the rules
Postgres views bypass row level security by default, because they run with the privileges of the user who created them, usually postgres. On Postgres 15 and later, creating the view with security_invoker = true makes it respect the policies of the person querying it. Functions declared security definer behave the same way and need the same scrutiny.
The Security Advisor flags security definer views, and it flags any view that exposes auth.users, which holds every user's email address.
5. Authorisation that trusts the user's own word
Supabase stores two kinds of user metadata. raw_user_meta_data can be updated by the signed-in user. raw_app_meta_data cannot. A policy or an admin check that reads a role from user metadata lets any user promote themselves.
Hiding the admin button is not the same as restricting admin access. If the only check is in the front end, the API will still answer anyone who asks directly.
6. Webhooks and server endpoints
A serverless function on Vercel or Netlify is a public URL. If it does not check who is calling, it answers everyone.
Payment webhooks are the sharpest version of this. A webhook handler that does not verify the provider's signature will accept "payment succeeded" from anyone who can send an HTTP request. Signature verification is a few lines and every major payment provider documents it.
Endpoints that call a paid AI model, send email or create accounts need rate limits. Without them, the first person to find the endpoint decides how big the bill is.
7. Auth settings that were fine in development
Supabase's built-in email service is not meant for production. Until custom SMTP is configured, it only delivers to addresses in your own project team, which means sign-up confirmations and password resets quietly stop reaching real customers. Supabase's production checklist also recommends email confirmation and a sensible expiry on one-time passwords.
Then there are the accounts that control everything. Multi-factor authentication belongs on the Supabase account, the hosting account, the Git host, the domain registrar and the payment provider.
8. Who holds the keys to the platform
Every account was created by the person building the app, on their own login, because that was the quickest way to ship.
The Supabase organisation.
The Vercel team.
The domain.
The payment account.
The repository.
The email inbox that receives the password resets for all of the above.
This is a very efficient arrangement until that person is on a plane. Supabase's production checklist recommends more than one owner on the organisation, and the same logic applies to every other account on that list.
9. Backups are a security control
A leaked secret key can delete data as easily as read it. On Supabase's Free plan there are no automatic backups. Paid plans include daily backups, and point-in-time recovery is an add-on. On every plan, database backups do not include files in Supabase Storage. We cover what to set up in Supabase backups and recovery.
10. Someone who is told
Every item above reduces the chance of something going wrong. None of them tells anyone when it does. Error tracking in Sentry, alerts on failed sign-ins and unusual database activity, and an engineer who receives those alerts are what turn a breach into an incident rather than a news story.
The checklist, in one list
- Row level security on every table in an exposed schema, with a real policy, not
using (true) - No secret, service role, payment or AI provider key in any
VITE_orNEXT_PUBLIC_variable, and any that ever were, rotated - Private storage buckets for anything customer-specific, with policies on
storage.objects - Views created with
security_invoker = true, andsecurity definerfunctions reviewed - Roles read from app metadata or a table, never from user metadata, and admin checks enforced on the server
- Webhook signatures verified, server endpoints authenticated, rate limits on anything that costs money
- Custom SMTP, email confirmation and a short one-time password expiry
- Multi-factor authentication and a second owner on every account that controls production
- Backups that include Storage, restored at least once
- Alerts that reach a person, at any hour the app is used
Security for an AI-built app is not a one-off. Every new table, bucket, function and environment variable is another chance for a default to go to production unreviewed. The checklist is the easy part. Running it after every release is the job.
That job is what we take on. Critical Cloud runs production for AI-built apps: monitoring, incident response, security review, and backups and recovery, on the stack the app already uses. You keep building the product. Production support for AI-built apps explains how it works, and Critical Support Lite is the service built for this stage.