How Missing RLS Nearly Killed an Event Ticketing Startup

TL;DR

For 6 months, an event ticketing platform 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. The team fixed it in hours, but the trust damage took months to repair. The startup nearly lost its biggest potential customer over it.

Building Fast, Securing Later

When the founder of an event ticketing platform started building the product, Supabase was the obvious choice because everyone said it was easy. And it was. Database in minutes. Auth in an hour. Features were shipping faster than ever.

The founder knew about Row Level Security. It was right there in the docs. But every time the team started to configure it, it felt complicated. The founder kept telling the team:

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

Six months later, the platform had 200 paying customers and RLS still hadn't been enabled.

The Demo That Changed Everything

The team was doing a demo for a potential enterprise customer - a major concert venue chain. Big deal for them. The venue's technical lead was watching as the founder walked through the product.

About halfway through, the technical lead stopped the demo. "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 the system. Names. Emails. Their private event data. Everything.

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

The founder did know. The team 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

The app filtered data by user ID on the client side. The team 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

The founder 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 had been put off for six months.

The Enterprise Deal

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

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

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

Notifying Existing Customers

The team 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 the company handled it.

What the Founder Learned

Security Is Product

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

RLS First, Features Second

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

Test What You Assume

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

The expensive lesson: "We'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 this startup's 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.

Security Stories

How Missing RLS Nearly Killed an Event Ticketing Startup