Supabase SecurityCornerstone guide

Supabase Security for AI-Built Apps: The Complete Guide

A plain-English guide to Supabase security for AI-built apps. Learn the real risks — RLS, service keys, storage buckets — and how to check your own app today.

Barret11 min read

If you built an app with Lovable, Bolt, v0, Replit, or a Cursor template, you almost certainly built it on Supabase — even if no one ever told you that. The AI wired it up for you. And now you've heard, from a Reddit thread or a scary scanner report, that Supabase apps can leak their users' data. You're not sure if yours is one of them.

Here's the calm version of the truth. Supabase is a solid database, and it is not insecure by nature. But Supabase security puts the responsibility on you — or, in the case of vibe-coded apps, on the AI that wrote your code. The model ships with a security setup that AI tools tend to leave half-done, because skipping the part that keeps your data private makes the app look finished faster.

This guide is the map. It explains why Supabase is the default backend for AI-built apps, why that design choice moves security onto the database, and the handful of risk areas that actually matter. Each one links to a deeper walkthrough. By the end you'll know exactly what to check and what's safe to ignore.

⚡ TL;DR

  • Supabase is the default backend for most vibe-coded apps, so for most founders, Supabase security is app security.
  • In a Supabase app, the browser talks straight to the database. That means the database itself has to enforce who can see what — through a feature called RLS (Row Level Security).
  • Your anon key is public by design and is not the emergency. The real risks are missing or permissive RLS, a leaked service_role key, and storage buckets left open.
  • You can check the big ones yourself in an afternoon, and most fixes are a few lines of SQL.

Why Supabase is the backend for vibe-coded apps

When you ask an AI builder to "let users sign up and save their data," it needs three things: a database, user login, and an API the frontend can call. Supabase bundles all three into one product. So the AI reaches for it almost every time.

This isn't a niche pattern. Supabase is the default backend for Lovable, Bolt, v0, Replit, and Cursor templates. Supabase reports around 10 million developers and 15.1 million databases created in 2025. Lovable alone was, as of late 2025, "closing in on 8 million users" with "100,000 new products on Lovable every single day," per its CEO. A large share of those products sit on Supabase.

The practical takeaway: if you're worried about your vibe-coded app's security, you're mostly worried about your Supabase configuration. That's why this is the longest guide on the site. Get Supabase right and you've closed most of the door.

The model that changes everything: the browser talks to the database

Most traditional web apps have a middle layer. The browser asks a backend server for data, the server checks "is this person allowed to see this?", and only then does it query the database. The server is the gatekeeper.

Supabase apps often skip that middle layer. The browser talks directly to the database over an auto-generated API. There's no server in between asking permission questions. This is what makes Supabase fast to build with — and it's the single fact that shapes everything about its security.

Because there's no server acting as the gatekeeper, the database has to be its own gatekeeper. It has to decide, on every single request, which rows this particular user is allowed to see or change. The feature that does that job is RLS. If RLS is off, the database hands back whatever is asked for. The gate stands open.

Think of the public API as the front door of a building, and the anon key as a door handle anyone can grab. That's fine — the building is supposed to be open to visitors. RLS is the set of locks on the individual apartments inside. Without those locks, walking through the front door means walking into everyone's home.

The rest of this guide walks the risk areas in roughly the order they matter.

Risk area 1: RLS — the one that leaks data

RLS, Row Level Security, is a Postgres feature (Supabase runs on Postgres) that filters table rows per user, on every query, inside the database itself. It works like an automatic WHERE clause you can't forget to add. With the right policy, a query for "all support tickets" quietly becomes "all support tickets belonging to this user."

When RLS is missing — or enabled with a policy that allows everything — anyone holding the public anon key can read the whole table. This is the number one way Supabase apps leak data, and it's the default state AI tools tend to leave you in, because a table with no restrictions "works" perfectly in the preview.

This risk is big enough that we cover it in two dedicated posts:

  • Start with Supabase RLS explained for the plain-English version of what RLS is and how to turn it on correctly.
  • Then read the USING(true) trap, because there's a subtle failure where RLS is technically enabled but the policy still leaks every row. A naive scanner reports "RLS on, you're safe" and misses it entirely.

If you only fix one thing on this list, fix RLS.

Risk area 2: the anon key vs the service_role key

This is the area where worried founders panic about the wrong thing, so let's set it straight.

Supabase gives your project two keys. The anon key is public by design — it's meant to live in your frontend, visible to anyone who opens the browser developer tools. The service_role key is the opposite: it's an all-powerful key that bypasses RLS entirely and is meant to live only on a trusted server, never in the browser.

🐺 Not a real problem

Finding your anon key in your site's JavaScript is not a leak. It's supposed to be there. The Supabase permission model assumes everyone can see it. If a scanner told you to rotate your anon key, it's crying wolf — and it probably missed the thing that actually matters.

The genuine emergency is the service_role key ending up in your frontend, your public Git repo, or a screenshot. Because it ignores RLS, a leaked service_role key gives an attacker full read and write access to your entire database — RLS policies and all. No lock can stop a key that's designed to open every door.

We pull these two apart in detail — what each does, how to tell them apart, and what to do if the wrong one leaked — in service_role vs anon key.

Risk area 3: storage buckets

Supabase Storage holds your files: profile photos, uploads, PDFs, ID documents. Buckets have their own access settings, separate from your table policies. And it's common for an AI builder — or a hurried founder — to make a bucket public so that images show up without fuss, without realizing that public means anyone on the internet can list and download everything in it.

A public bucket full of avatars might be fine. A public bucket full of uploaded invoices, medical files, or government IDs is a serious exposure. The most damaging vibe-coding breaches in 2025 involved file storage left open to the world, not clever hacking.

Supabase storage bucket security walks through how to tell whether a bucket is public, when public is acceptable, and how to lock down the ones that shouldn't be.

Risk area 4: actually testing your policies

Here's the trap that catches even careful builders: a policy can look correct in the dashboard and still be wrong. The dashboard shows you an on/off switch and the policy text. It does not show you what your live endpoints actually return to a stranger.

The only honest test is to act like three different people and see what each one gets back: an anonymous visitor with the public anon key alone, the owner of a row, and a different logged-in user trying to reach the owner's data. If the anonymous visitor or the other user can pull rows they shouldn't, your policy is broken — no matter how good it looks in the UI.

Testing RLS the right way shows you exactly how to run those three checks. This is the difference between believing you're secure and knowing it.

Risk area 5: learn from CVE-2025-48757

You don't have to take any of this on faith. The pattern is documented.

CVE-2025-48757 was the first published CVE for this exact category, disclosed in May 2025. A scan of 1,645 Lovable-built apps found 170 of them — about 10% — leaking real user data across 303 endpoints. Every one of those leaks came through the public anon key hitting tables that had missing or USING(true) RLS policies. The same pattern has since shown up in apps built on Bolt, v0, Cursor, and Replit. It is not a bug in any one tool; it's a default the whole ecosystem inherits.

The reason this CVE matters to you is that it's a precise blueprint of the most common Supabase failure — and proof that people are actively scanning for it. We break the whole thing down, in plain language, in the CVE-2025-48757 breakdown.

How to check your own app

You can run a meaningful self-audit today. Here's the short version; the linked posts go deeper on each step.

  1. Confirm RLS is on for every table with user data. In the Supabase dashboard, open Database → Tables (or Authentication → Policies). Any table holding real user data should show RLS enabled. "RLS disabled" on a real table is a likely leak.
  2. Read the policy, not only the switch. A policy of USING (true) matches every row, which is the same as no protection at all. Look for auth.uid() = user_id style conditions instead.
  3. Find your service_role key — then make sure it's nowhere public. It should never appear in frontend code, a client-side environment variable, or a public repo. The anon key in your frontend is fine.
  4. Check your storage buckets. In Storage, see which buckets are marked public. Ask yourself whether the files inside are safe for anyone to download.
  5. Test as a stranger. Query one of your own API endpoints with only the anon key and see what comes back. If you can read other people's rows, so can anyone.

Not sure if your app has this exact issue?

Run a free, read-only scan of your live app — no install, results in under a minute.

Scan my app free →

Supabase security best practices, in order

If you want a priority list to hand to your AI builder or work through yourself, this is it:

  1. Enable RLS on every table that stores user data. No exceptions.
  2. Write real policies. Tie each row to its owner with auth.uid() = user_id. Never ship USING (true).
  3. Keep the service_role key server-side only. If it ever touched the browser or a public repo, rotate it.
  4. Default storage buckets to private. Make a bucket public only when the files truly are public, like a logo.
  5. Test as anon, owner, and other-user before you call a feature done.
  6. Re-check after every AI change. Each new feature can add a new table with no policy. Security isn't a one-time task on a tool that ships code daily.

A copy-paste prompt for your AI builder that covers the core of this:

Enable Row Level Security on every table that stores user data.
For each one, add policies so a user can only read and write rows
where user_id equals their auth.uid(). Do not use USING(true).
Keep the service_role key server-side only — never in client code.

FAQ

Is Supabase secure?

Yes — Supabase itself is a well-built, production-grade database. The risk in vibe-coded apps isn't Supabase's code; it's the configuration. Because the browser talks directly to the database, you are responsible for turning on RLS, writing real policies, protecting the service_role key, and securing storage buckets. AI builders often leave those undone. Supabase gives you the locks; someone has to actually lock the doors.

Is it safe that my Supabase URL and anon key are visible in my app?

Yes. Both the project URL and the anon key are public by design and are meant to live in your frontend. Your security does not come from hiding them — it comes from RLS policies on your tables. The one key you must never expose is the service_role key.

What's the single most important Supabase security setting?

Row Level Security. In a Supabase app the database is the gatekeeper, and RLS is the rule it enforces on every query. A table with RLS off, or with a USING (true) policy, will hand its data to anyone. Get RLS right and you've closed the most common and most damaging gap.

How often should I check my Supabase security?

After every meaningful change. The defining feature of vibe coding is that your AI ships new code — and potentially new tables, endpoints, and buckets — constantly. A table that was safe last week might have a sibling added today with no policy at all. Treat security as a continuous check, not a launch-day chore.

The bottom line

For most vibe-coded apps, Supabase security is the security story, because the browser talks straight to the database and the database has to defend itself. The good news is that the risks are few and learnable: turn on RLS with real policies, keep the service_role key off the frontend, lock down storage buckets, and test as a stranger. Your anon key being public is not the problem — never was. An attacker can find an open table or a leaked service key in minutes by reading your network traffic. The point is to find it first, and to keep checking, because your AI adds new code and new gaps on every deploy.

Find your gaps before an attacker does.

Is My Site Hackable? scans your deployed app for the exact issues in this article — exposed keys, missing RLS, open buckets — and tells you what's real and what's a false alarm.

Run a free scan →