TL;DR
My Supabase database was publicly accessible for three weeks. No Row Level Security, no authentication checks on queries. A security researcher found it and reported it. I was lucky it wasn't an attacker. The fix took 30 minutes. Understanding why it happened took much longer.
How It Started
I was building a SaaS app for small businesses. Nothing groundbreaking, but it handled customer data: names, emails, business information. I chose Supabase because it was fast to set up and the free tier was generous.
My approach was common among vibe coders: get it working first, secure it later. The problem is "later" has a way of never coming.
"I'll add RLS when I have more users. Right now it's just me testing."
Three weeks went by. I had 47 users. Real people with real data in my database. And I still hadn't enabled Row Level Security.
The Email That Changed Everything
The email came on a Thursday afternoon. Subject line: "Security vulnerability in your application."
"Hi, I'm a security researcher. I found your Supabase project URL in your client-side code. Using just the anon key, I was able to query all tables and retrieve complete user data including emails, names, and payment information. This data is currently exposed to anyone who knows how to look."
My hands were shaking as I read it. I opened my Supabase dashboard and tried querying the users table without authentication. It returned everything.
What Was Exposed
I ran an inventory of what could have been accessed:
- 47 user accounts with full names and email addresses
- Business information including company names and addresses
- Subscription data (thankfully, Stripe handles actual payment info)
- Internal notes I'd made about customer conversations
- API usage logs showing what each user had accessed
The security researcher was kind. They hadn't extracted or shared the data. They just wanted to help me fix it. Not everyone who finds vulnerabilities is so ethical.
Why It Happened
The Supabase Anon Key
When you create a Supabase project, you get two keys: an "anon" key and a "service_role" key. The anon key is designed to be used in client-side code. It's not secret.
The security model assumes you'll use Row Level Security (RLS) to control what the anon key can access. Without RLS, the anon key can query everything.
// This is how easy it was to access my data
const supabase = createClient(
'https://[my-project].supabase.co',
'eyJ...[anon-key]...'
);
// Without RLS, this returns ALL users
const { data } = await supabase.from('users').select('*');
My Mental Model Was Wrong
I thought of the anon key like an API key. Something to keep secret. But Supabase is designed differently. The anon key is public. The protection comes from RLS policies, not from hiding the key.
Every tutorial I'd followed showed how to connect to Supabase. None of them emphasized that the connection wasn't the security. The policies were.
The Fix
The actual fix took about 30 minutes:
- Enabled RLS on all tables
- Created policies that require authentication and limit users to their own data
- Tested that unauthenticated queries now return nothing
-- Enable RLS on the users table
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
-- Users can only see their own data
CREATE POLICY "Users can view own data"
ON users FOR SELECT
USING (auth.uid() = id);
-- Users can only update their own data
CREATE POLICY "Users can update own data"
ON users FOR UPDATE
USING (auth.uid() = id);
30 minutes. That's all it took. But for three weeks, I'd been "too busy" to do it.
The Aftermath
Notifying Users
I wrestled with whether to notify users. Legally, I probably should have. But I had no evidence the data was actually accessed by anyone malicious. The security researcher had been responsible.
In the end, I sent a brief email explaining that I'd discovered and fixed a security issue. I didn't go into detail about how bad it was. Looking back, I should have been more transparent.
The Emotional Toll
For weeks after, I second-guessed everything. Every new feature got reviewed multiple times. I'd wake up at 3 AM thinking about other things I might have missed. The imposter syndrome was real.
The hardest part: I had to accept that I'd put 47 people's data at risk because I was moving too fast. There's no way to undo that. Only to do better going forward.
What I Changed
Security-First Mindset
I no longer add features without considering security implications. RLS policies are part of the database schema design, not an afterthought.
Regular Security Audits
Every week, I run through a checklist. Can unauthenticated users query this? Can User A access User B's data? It takes 15 minutes and has caught several issues.
Automated Testing
I added tests that specifically try to access data without proper authentication. If they succeed, the test fails and the deploy is blocked.
Learning Supabase Properly
I finally read the Supabase security documentation. All of it. Understanding how the platform actually works (not just how to make it work) made all the difference.
Is the Supabase anon key supposed to be secret?
No. The anon key is designed to be used in client-side code and is not secret. Security comes from Row Level Security policies, not from hiding the key. Your service_role key, however, should never be exposed.
How do I know if my Supabase database is exposed?
Try querying your database using just the anon key without any user authentication. If you can retrieve data that should be protected, your RLS policies are missing or misconfigured.
Should I notify users if their data was potentially exposed?
Generally, yes. Many jurisdictions require breach notification. Even if not legally required, being transparent builds trust. Consult with a lawyer if you're unsure about your specific situation.
What's the minimum RLS setup I need?
At minimum, enable RLS on every table and create policies that restrict access to authenticated users viewing their own data. You can add more complex policies as needed, but this baseline prevents the most common exposures.
Is Your Database Exposed?
Scan your Supabase project for missing RLS and other vulnerabilities.
Start Free Scan