NeuroStrike

Supabase RLS: The Mistake Every AI App Makes

NeuroStrike Research

Security Research Team

|3 min read

If you have built an application with Supabase — especially using an AI coding tool like Lovable, Bolt, or Cursor — there is a high probability your database is not properly secured. Row Level Security (RLS) is the feature that controls who can read and write what in your database, and it is the single most misconfigured feature in the Supabase ecosystem.

What Row Level Security Is

Supabase uses PostgreSQL under the hood. PostgreSQL has a feature called Row Level Security that lets you define policies controlling which rows a user can select, insert, update, or delete. Without RLS, anyone with a valid database connection can read every row in every table. With RLS enabled and properly configured, each user can only access their own data.

RLS is not optional in any application that stores user data. It is the difference between a secure app and a data breach.

Find what scanners miss

NeuroStrike runs autonomous breach simulations that go beyond checkbox security testing.

Start Free

The Vulnerable Pattern

Here is what AI-generated code typically looks like. The Supabase table is created without RLS:

CREATE TABLE profiles (
  id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id uuid REFERENCES auth.users(id),
  display_name text,
  email text,
  created_at timestamptz DEFAULT now()
);

-- No RLS enabled
-- No policies defined
-- Any authenticated user can SELECT * FROM profiles

The app works perfectly in development. The developer logs in, sees their profile, and everything looks correct. But any authenticated user can run this query and get every user's data:

const { data } = await supabase
  .from("profiles")
  .select("*")
// Returns ALL profiles, not just the current user's

The Secure Pattern

Here is what the same table should look like:

CREATE TABLE profiles (
  id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id uuid REFERENCES auth.users(id),
  display_name text,
  email text,
  created_at timestamptz DEFAULT now()
);

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

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

-- Users can only update their own profile
CREATE POLICY "Users can update own profile"
  ON profiles FOR UPDATE
  USING (auth.uid() = user_id);

-- Users can insert their own profile
CREATE POLICY "Users can insert own profile"
  ON profiles FOR INSERT
  WITH CHECK (auth.uid() = user_id);

With these policies, the same query only returns the current user's profile. The database enforces the access control, regardless of what the client-side code does.

Find what scanners miss

NeuroStrike runs autonomous breach simulations that go beyond checkbox security testing.

Start Free

Common RLS Mistakes

Mistake 1: RLS Enabled but No Policies

Enabling RLS without creating any policies blocks all access, including legitimate access. Developers see their app break and either disable RLS or add an overly permissive policy.

Mistake 2: The 'Allow All' Policy

-- DO NOT DO THIS
CREATE POLICY "allow all" ON profiles
  FOR ALL USING (true);

This policy enables RLS technically but allows every user to do everything. It is security theater.

Mistake 3: Missing DELETE Policy

Developers add SELECT, INSERT, and UPDATE policies but forget DELETE. A malicious user can delete other users' data.

Mistake 4: Using Service Role Key Client-Side

The Supabase service_role key bypasses all RLS policies. It should only be used server-side. If your client-side code uses the service_role key, your RLS policies are meaningless.

How to Audit Your Existing Project

Open the Supabase SQL Editor and run this query:

SELECT
  schemaname,
  tablename,
  rowsecurity
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY tablename;

Any table where rowsecurity is false needs immediate attention. Then check your policies:

SELECT
  schemaname,
  tablename,
  policyname,
  permissive,
  roles,
  cmd,
  qual
FROM pg_policies
WHERE schemaname = 'public'
ORDER BY tablename, policyname;

Review each policy. Look for policies where qual is 'true' (allows everything) or where the user_id check is missing.

Beyond Manual Checks

Manual SQL audits catch the obvious issues. But RLS is just one layer of application security. Your app may also have client-side data leaks, broken auth flows, missing input validation, and other vulnerabilities that AI coding tools introduce.

NeuroStrike tests your entire application — not just the database layer — and produces actionable findings with proof-of-concept exploits. If you built with Lovable, Bolt, or Cursor and you use Supabase, sign up and run a scan. The RLS issues are just the beginning.

Find what scanners miss

NeuroStrike runs autonomous breach simulations that go beyond checkbox security testing.

Start Free

Related Posts

Supabase RLS: The Mistake Every AI App Makes | NeuroStrike | NeuroStrike