Building an app with AI is incredible — until you push it to the internet with no protection. And this is the most dangerous moment of the whole journey: AI, in builder mode, focuses on making the app WORK, not on how it can be attacked. The result: gorgeous apps where anyone can see another user's data by changing a number in the URL, APIs that any site in the world can call, and browsers with no defense instructions. This guide covers the 3 most-forgotten locks — RLS, CORS, and security headers — with an analogy for each one, the real risk, and a detailed prompt you paste into your AI so it sets everything up for you. You don't need to know how it works under the hood: just what each thing is called and when to put it in place.
The moment is clear and non-negotiable: right before you publish your app or hand it to real users. While you're testing on your own machine, nothing happens. But the second your app is on the internet with other people's data, without these locks you're exposing everything. And since AI won't do it on its own (it's thinking about making it work, not about protecting it), it's on you to ask for it explicitly.
Without RLS, a malicious user just needs to change an ID in the URL or in a request to see someone else's data. It's the most common vulnerability in Supabase and PostgreSQL apps — and the easiest to exploit. That's why it's lock #1.
I'm using Supabase with PostgreSQL. I have these tables: [list your tables and their user columns, e.g.: posts (user_id), comments (post_id, user_id)]. Set up Row-Level Security so each user can only view/edit/delete their own records. Follow Supabase's official best practices: - Enable RLS on ALL tables (ENABLE ROW LEVEL SECURITY). - Create SEPARATE policies for SELECT, INSERT, UPDATE and DELETE (do NOT use FOR ALL). - IMPORTANT for performance: wrap the user function as (SELECT auth.uid()), NOT a bare auth.uid(), so Postgres evaluates it once per query instead of once per row. - Restrict policies to the authenticated role (TO authenticated), not public, so anonymous visitors can't touch the table at all. - NEVER use user_metadata in policies (the user can modify it). - Add indexes on the columns the policies use (e.g. user_id). - Remember: for an UPDATE to work, you also need a SELECT policy. - Give me the complete SQL ready to paste into the Supabase SQL Editor, and explain in one line what each policy does.
auth.uid() directly in the policy. BUT that makes Postgres evaluate it once for every row — on a table with 100,000 rows, the difference is between a 5-millisecond query and a 5-second one! The trick (documented by Supabase): write it as `(SELECT auth.uid())`. That way it's evaluated just once. That's why the prompt above asks for it explicitly.Configure CORS on my backend so that ONLY my production domain (https://my-app.com) and localhost in development can make requests. - Do NOT use "*" as an allowed origin in production. - Only allow the methods I actually use (e.g. GET, POST, PUT, DELETE). - Only allow the headers I need. - Explain exactly where to put each thing based on my stack: [tell it your framework/backend].
Access-Control-Allow-Origin: * with credentials enabled. It's like leaving the door open and hanging a sign that says "come on in, I left the keys inside." If your AI set it up that way "to make it work fast," fix it before you ship.Add the recommended security headers to my app: Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Strict-Transport-Security (HSTS) and Permissions-Policy. - Explain what each one does in one simple line. - Give me the config ready for my framework: [tell it which you use, e.g. Next.js, Express]. - Start the Content-Security-Policy in report-only mode so nothing breaks, and tell me how to tighten it afterward. - Tell me how to verify the result.
* with credentials.Join 4,200+ builders. No credit card. Build your first app with AI in minutes.