How Missing RLS Nearly Killed My Startup

Share

TL;DR

For 6 months, my SaaS had no Row Level Security on Supabase. Any user could query any other user's data with basic browser dev tools. A technical customer discovered it during a demo. We fixed it in hours, but the trust damage took months to repair. We nearly lost our biggest potential customer over it.

Building Fast, Securing Later

When I started building my SaaS, I chose Supabase because everyone said it was easy. And it was. Database in minutes. Auth in an hour. I was shipping features faster than ever.

I knew about Row Level Security. I'd seen it in the docs. But every time I started to configure it, it felt complicated. I'd tell myself:

"I'll add RLS when we have more users. Right now, it's just me and a few beta testers. Security can wait."

Six months later, we had 200 paying customers and I still hadn't enabled RLS.

The Demo That Changed Everything

We were doing a demo for a potential enterprise customer. Big deal for us. Their technical lead was watching as I walked through the product.

About halfway through, she stopped me. "Can I try something?" she asked.

She opened browser dev tools, found a network request to Supabase, and modified the query. She removed the user ID filter.

The response came back with data from every user in our system. Names. Emails. Their private project data. Everything.

The room went silent. She looked at me. "You know this is a critical vulnerability, right?"

I did know. I just hadn't fixed it.

Why This Happened

The Supabase Security Model

Supabase uses a different security model than traditional backends. Your anon key is public. It's embedded in your frontend code. Everyone can see it.

The security comes from Row Level Security policies. Without RLS, anyone with the anon key can query any data. And since the key is public, that means everyone.

// Without RLS, this query returns ALL users
const { data } = await supabase
  .from('projects')
  .select('*')

// Even if your app filters by user_id,
// attackers can remove that filter

Client-Side Security Is Not Security

My app filtered data by user ID on the client side. I thought that was enough. It's not.

Client-side code can be modified. Network requests can be intercepted and changed. If the server doesn't enforce access controls, the access controls don't exist.

The Aftermath

The Immediate Fix

I stayed up all night enabling RLS on every table. The actual fix wasn't complicated:

-- Enable RLS
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;

-- Users can only see their own projects
CREATE POLICY "Users see own projects"
  ON projects FOR SELECT
  USING (auth.uid() = user_id);

-- Users can only modify their own projects
CREATE POLICY "Users modify own projects"
  ON projects FOR ALL
  USING (auth.uid() = user_id);

It took about 4 hours to properly secure all tables. Four hours of work that I'd been putting off for six months.

The Enterprise Deal

The technical lead's report to her team was not favorable. They had concerns about our security practices. We had to do a full security review, bring in an external auditor, and provide documentation of our security processes.

The deal almost fell through. We only saved it by being completely transparent about what happened and what we changed. The whole process took three months.

The hidden cost: That three-month delay in closing the deal cost us roughly $45,000 in potential revenue, not to mention the $8,000 we spent on the security audit. The "time saved" by skipping RLS was negative.

Notifying Existing Customers

We made the difficult decision to notify all customers about the vulnerability. Some were understanding. Others were furious. Two customers left immediately.

But most appreciated the honesty. The customer who found the issue? She became an advocate after seeing how we handled it.

What I Learned

Security Is Product

I used to think of security as separate from product work. Something to add later. Now I understand that security IS the product. If your app isn't secure, it doesn't matter how many features it has.

RLS First, Features Second

Now, whenever I create a new table in Supabase, the very first thing I do is enable RLS and write policies. Before I write any application code. It's part of the schema design, not an afterthought.

Test What You Assume

I assumed my client-side filtering was sufficient. I never tested it. Now, I have automated tests that specifically try to access other users' data. If they succeed, the test fails.

The expensive lesson: "I'll add security later" is a form of technical debt that compounds faster than feature debt. The cost of adding it later isn't just the development time. It's the potential data exposure, lost deals, damaged reputation, and customer trust that's incredibly hard to rebuild.

Advice for Other Founders

If you're using Supabase (or any similar platform), please learn from my mistake:

  1. Enable RLS on day one. Before you have users. Before you have data. Make it part of your initial setup.
  2. Test your security. Try to access data you shouldn't be able to access. If you can, so can attackers.
  3. Understand the security model. Supabase's security model is different from traditional backends. Read the docs. Understand how the anon key works.
  4. Don't trust client-side code. Any security check that happens in the browser can be bypassed. Server-side enforcement is the only real security.

What is Row Level Security (RLS)?

RLS is a PostgreSQL feature that lets you control which rows a user can access based on policies you define. In Supabase, RLS is essential because the anon key is public and embedded in your frontend code.

How do I know if my Supabase project is vulnerable?

Open browser dev tools, find a Supabase query, and try modifying it to remove user filters. If you can retrieve other users' data, RLS is missing or misconfigured. You can also check the Supabase dashboard to see which tables have RLS enabled.

Is the Supabase anon key supposed to be secret?

No. The anon key is designed to be public and embedded in frontend code. Security comes from RLS policies, not from hiding the key. Your service_role key, however, should never be exposed.

Can I use Supabase without RLS?

Technically yes, but only if you exclusively use the service_role key from a secure backend and never expose database access to the client. For most applications, especially those with a frontend, RLS is essential.

Is Your Database Exposed?

Scan your Supabase project for missing RLS and other vulnerabilities.

Start Free Scan
Security Stories

How Missing RLS Nearly Killed My Startup