How to secure a Supabase app built with AI tools
Supabase has become the default backend for AI-built applications. It offers a hosted Postgres database, authentication, storage, and a JavaScript client that AI tools know how to use from training data — all of which makes it the natural choice when an AI builder scaffolds a backend. That concentration is also what makes it a concentrated risk surface. When a Supabase project is misconfigured, the consequences are not limited to a single feature or endpoint; the entire database and all its data is potentially exposed. Before you share a launch link for any AI-built app backed by Supabase, there are six specific things you need to verify.
The most important thing to understand about Supabase credentials is the difference between the anon key and the service role key. The anon key is a JWT that represents the anonymous (unauthenticated) role. It is safe to use in client-side code, but it is only safe in the presence of Row Level Security policies that restrict what the anon role can do. The service role key bypasses RLS entirely and has full administrative access to your database. It should never appear in client-side code, never be prefixed with NEXT_PUBLIC_, and never be committed to a repository. It belongs in server-side environment variables only — accessed from API routes and server components, never bundled into the browser. AI builders frequently place the service role key in the same location as the anon key, treating them as interchangeable. They are not.
Row Level Security is the database-native access control layer that makes the anon key safe to expose. Enabling it is a single toggle in the Supabase table editor, but the effect is significant: once RLS is on, all access to that table is denied by default until you add explicit policies. The most common policy for user-scoped data looks like this in SQL: CREATE POLICY "users can only see their own rows" ON profiles FOR SELECT USING (auth.uid() = user_id); This tells Postgres that a request can only read rows where the user_id column matches the user ID from the authenticated JWT. Write similar policies for INSERT, UPDATE, and DELETE. The service role bypasses all of these policies, which is why it must never reach the client — once an attacker has it, RLS offers no protection.
Auth-gating your routes on the server side is the next layer. In a Next.js app, this means checking the session in server components and API route handlers, not relying on client-side redirects. A common mistake in AI-generated Next.js apps is using a client-side useEffect to redirect unauthenticated users away from a dashboard page. The redirect works visually, but the page still renders its data-fetching code before the redirect fires, and an API route called by that page has no session check of its own. A determined attacker can intercept the API call directly, bypassing the UI redirect entirely. Every API route that returns user data or performs user actions must independently verify the session using auth().protect() or an equivalent server-side check.
Supabase Storage bucket policies are a fourth area where AI-generated apps commonly leave gaps. A public bucket allows anyone to access any file by URL. This is appropriate for static assets like marketing images, but not for user-uploaded content like profile photos, documents, or private files. AI builders default to public buckets because they are simpler to scaffold. Check your bucket settings in the Supabase dashboard and confirm that buckets containing user-generated content have RLS-style access policies that restrict download to the file's owner or to authenticated users only.
Before your launch, run through this five-point Supabase security checklist. First, confirm the service role key is not in any NEXT_PUBLIC_environment variable. Second, open the Supabase table editor and verify that RLS is enabled on every table — the toggle shows a green checkmark when active. Third, test your RLS policies by using the Supabase dashboard's "Test as role" feature to impersonate the anon user and confirm they cannot read rows they should not see. Fourth, check every Storage bucket and confirm that user-upload buckets are not public. Fifth, run a scanner against your deployed URL to verify that your service role key does not appear in any client-side JavaScript. All five of these take less than thirty minutes and protect you against the most common Supabase-related incidents.