Security

6 min

What Actually Happens When Your Supabase RLS Is Misconfigured

What Actually Happens When Your Supabase RLS Is Misconfigured

83% of Supabase database exposures trace back to disabled Row Level Security. Here's what an unprotected table actually looks like to an attacker, including the Moltbook breach and CVE-2025-48757, plus a 30-second way to check your own app.

83% of Supabase database exposures trace back to disabled Row Level Security. Here's what an unprotected table actually looks like to an attacker, including the Moltbook breach and CVE-2025-48757, plus a 30-second way to check your own app.

So, if you've been following along with the audit findings I've been sharing here, you'll have noticed that I mention something called RLS fairly often when breaking down Supabas -based apps. I've had a fair number of people ask me what it actually is and why it keeps coming up, so I figured it was worth writing this one out properly, because I genuinely think it's one of the most important things a Supabase founder can understand right now.

The short version: 83% of Supabase database exposures trace back to RLS misconfiguration. Here's what that actually means in practice.


First, what RLS actually is

Supabase is built on top of PostgreSQL, and one of the things PostgreSQL offers is something called Row Level Security. In plain terms, RLS is a set of rules you define at the database level that control which rows of data a given user is actually allowed to see or interact with. Not at the app layer, not in your frontend code, but in the database itself.

The way Supabase works, it automatically generates REST API endpoints from your database tables, which is a large part of what makes it so fast to build with. The anon key, which is the public key that your app ships with and that your frontend uses to talk to Supabase, is entirely intentional and expected to be exposed. That part is by design. The security model is built around the assumption that RLS will be in place to control what that key can actually access.

The problem is that RLS is off by default. You have to go and turn it on yourself, and then define the policies for each table. And as it turns out, a very significant number of founders are either not aware of this, or are shipping before they've done it.


What "off" actually looks like to an attacker

This is the part I think most founders genuinely don't have a clear picture of, so it's worth being very concrete about it. When RLS is disabled on a table, anyone with your anon key and your project URL can query that table directly and get back every single row in it. Not just their own data. Every row. Every user. Everything.

And it gets worse than a simple read. Supabase's auto-generated API supports powerful query filters, which means an attacker doesn't just get a data dump, they can query your database with real precision. Filtering by specific fields, narrowing to specific users, extracting exactly what they want. No authentication required, no account needed, just your anon key which is almost certainly sitting in your frontend bundle already.

Cognisys documented a particularly striking real world example of this. During an audit, they found a table called UserSecuritySettings sitting fully exposed with RLS disabled. That table contained security PINs and OTP secrets controlling MFA for every user on the platform. And it wasn't just readable. It was writable. So not only could an attacker extract every user's MFA configuration, they could modify it.

That's not a theoretical scenario. That's a documented audit finding from late 2025.


The Moltbook incident

If you want a concrete example of what this looks like at scale, Moltbook is probably the most well documented recent case. It was a social network for AI agents that launched on January 28th 2026. By January 31st, two independent security researchers had found and reported the same critical flaw: RLS was never enabled, the Supabase API key and project URL were exposed in the client-side JavaScript, and the combination of those two things meant the entire database was sitting wide open to unauthenticated read and write access.

The exposure included approximately 1.5 million API authentication tokens, 35,000 email addresses, 4,000 private messages, and 4.75 million total database records. Three days after launch.

And this wasn't a case of a sophisticated attacker doing something clever. It was a configuration left at its default state, found by researchers using fairly standard tooling.


Why AI tools make this worse specifically

CVE-2025-48757 is worth understanding here because it illustrates something important about how AI tools interact with this problem. The vulnerability affected over 170 Lovable-generated applications, and the root cause was that Lovable had been generating Supabase database schemas without RLS policies as part of its default output. Every app generated through that pattern inherited the same misconfiguration, not because of anything the individual founder did, but because of what the template produced by default.

Lovable has since updated their code generation to include RLS by default, which is a meaningful improvement. But the broader point stands: AI tools building on Supabase have a consistent track record of not configuring this correctly, and the founder typically has no visibility into whether it's been handled or not, because the app functions completely normally either way. RLS being off doesn't break anything, it just leaves everything exposed.

From my own audits, this is one of the most consistently present findings I see across Supabase based builds regardless of which tool was used to generate them.


One thing worth knowing about the service_role key

While we're here, there's a related distinction that's worth being clear on because I see it confused fairly often. Supabase has two primary keys. The anon key is public by design and is expected to be visible in your frontend code, as covered above. The service_role key is a completely different matter. It bypasses RLS entirely regardless of whatever policies you've set, and it should never, under any circumstances, appear in client-side code. If it does, RLS is irrelevant because that key ignores it completely.

So when doing a bundle check on your frontend code, finding the anon key is a finding worth flagging and investigating further. Finding the service_role key is an immediate critical severity issue regardless of anything else in your configuration.


How to check your own app in about 30 seconds

There is a fairly straightforward way to check whether your tables are exposed. Take your Supabase project URL and anon key, and run the following, swapping in your own values:

curl -X GET 'https://your-project.supabase.co/rest/v1/users?select=*'
-H "apikey: YOUR_ANON_KEY"

A secure response comes back as an empty array. If you see actual user records, email addresses, or any real data come back, your table is exposed and RLS needs to be enabled and configured immediately.

Beyond that, the fix itself lives in your Supabase dashboard under the Table Editor. Select a table, click Edit Table, and toggle on Row Level Security. From there you define policies, which are SQL rules that tell the database who is allowed to access what. The most common and basic one for a users table looks something like: users can only select rows where the user ID matches their own authenticated ID. Simple in concept, but it needs to be explicitly written and applied to every table individually.

If you've shipped a Supabase app and haven't verified this, it is genuinely worth taking 30 minutes to go and check. The exposure pattern here is one of the most commonly documented in the current AI-built app landscape, and it's one where the fix, once you know what you're looking for, is pretty straightforward to implement.


Back to blog