← All posts
supabase rls policy checkersupabase securityvibe codingrow level securityai built apps

Supabase RLS Policy Checker for Vibe-Coded Apps: A Practical Guide

O

OverMCP Team

Quick answer

A Supabase RLS policy checker scans your database tables for missing or misconfigured row-level security policies, which are the most common security hole in vibe-coded apps. Without proper RLS, any authenticated (or sometimes unauthenticated) user can read, write, or delete data they shouldn't have access to. Use a dedicated scanner or manual queries to audit your policies before going live.

What to check first

Before diving into code, run through this checklist to spot obvious RLS issues in your Supabase project:

  • Enable RLS on every table – Go to your Supabase dashboard > Authentication > Policies. Verify every public table has RLS toggled ON. Tables without RLS are wide open.
  • Check for "public" policies – Avoid policies that use true or 1=1 as the USING expression. These allow all access regardless of user.
  • Verify authenticated-only access – Ensure policies use auth.uid() or auth.role() to restrict data to the correct user.
  • Test with anonymous access – Try fetching data from your app while logged out. If you see real data, RLS is missing or broken.
  • Review policy for insert/update/delete – Each operation (SELECT, INSERT, UPDATE, DELETE) may need its own policy. Don't assume a SELECT policy covers writes.
  • Look for leaked service role keys – If you accidentally exposed your service_role key (which bypasses RLS), a scanner like our secret leak scanner can catch it.
  • How to use a Supabase RLS policy checker

    A Supabase RLS policy checker can be a database query, a CLI tool, or an automated scanner. Here's a step-by-step workflow you can run directly in your Supabase SQL editor.

    Step 1: Query tables without RLS

    Run this SQL to list all tables that have RLS disabled:

    SELECT schemaname, tablename
    FROM pg_tables
    WHERE schemaname = 'public'
      AND tablename NOT IN (SELECT tablename FROM pg_policies WHERE schemaname = 'public');

    This returns all public tables missing any policy. If you see tables here, you need to enable RLS and create policies.

    Step 2: List all existing policies

    Review every policy for potential gaps:

    SELECT *
    FROM pg_policies
    WHERE schemaname = 'public';

    Pay attention to the using and with_check expressions. Look for:

  • USING (true) – allows anyone
  • USING (auth.uid() IS NOT NULL) – allows any authenticated user (may be too broad)
  • Missing WITH CHECK – for INSERT/UPDATE, you need both USING (for existing rows) and WITH CHECK (for new/modified rows)
  • Step 3: Test policies with a non-admin user

    Create a test user or use the anon key to simulate a real request. In your app, call:

    // Using supabase-js
    const { data, error } = await supabase
      .from('profiles')
      .select('*')
      .limit(1);
    
    if (error) {
      console.error('RLS blocked access:', error.message);
    } else {
      console.log('Data returned (RLS may be too permissive):', data);
    }

    If you get data without being logged in, your RLS is broken.

    Step 4: Automate the check with a scanner

    Instead of running SQL manually every time, use a dedicated free AI app security scanner that checks Supabase RLS policies automatically. It connects to your project and reports missing or misconfigured policies in seconds.

    Common mistakes in vibe-coded apps

    AI coding tools like Cursor, Bolt.new, and Lovable often generate Supabase code that looks secure but has hidden RLS holes:

  • Copy-pasted policies from tutorials – Many tutorials use USING (true) for simplicity. AI models trained on these examples may replicate them.
  • Forgetting to enable RLS on new tables – When you ask AI to "add a comments table," it creates the table but rarely enables RLS. Every new table is a potential leak.
  • Using `auth.uid() = user_id` but user_id is nullable – If user_id can be null, the policy may return true for unauthenticated users.
  • Only securing SELECT, not INSERT/UPDATE/DELETE – Your data is safe to read, but anyone can delete it.
  • Exposing the service_role key in client code – This key bypasses all RLS. A common AI mistake is putting it in a .env file that gets leaked. Our continuous security monitoring can alert you if such keys appear in your repo.
  • Step-by-step fix for common RLS gaps

    Fix 1: Enable RLS on a table

    ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;

    Then create a policy:

    CREATE POLICY "Users can view their own profile"
    ON public.profiles
    FOR SELECT
    USING (auth.uid() = id);

    Fix 2: Add policies for all operations

    Don't forget INSERT and UPDATE:

    CREATE POLICY "Users can insert their own profile"
    ON public.profiles
    FOR INSERT
    WITH CHECK (auth.uid() = id);
    
    CREATE POLICY "Users can update their own profile"
    ON public.profiles
    FOR UPDATE
    USING (auth.uid() = id)
    WITH CHECK (auth.uid() = id);

    Fix 3: Restrict access to authenticated users only

    If you need any authenticated user to read data (e.g., a public feed), use:

    CREATE POLICY "Authenticated users can read posts"
    ON public.posts
    FOR SELECT
    USING (auth.role() = 'authenticated');

    Avoid USING (true) unless the table is meant to be fully public (like a list of countries).

    Fix 4: Use a helper function for complex checks

    For multi-tenant apps, create a reusable function:

    CREATE OR REPLACE FUNCTION public.is_org_member(org_id bigint)
    RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
      SELECT EXISTS (
        SELECT 1 FROM public.org_members
        WHERE user_id = auth.uid() AND org_id = org_id
      );
    $$;

    Then use it in policies:

    CREATE POLICY "Org members can view projects"
    ON public.projects
    FOR SELECT
    USING (public.is_org_member(org_id));

    Common mistakes

  • Trusting AI-generated policies blindly – Always review and test policies manually. AI can't reason about your specific data model.
  • Not testing with a real client – The Supabase dashboard shows policies exist, but only a real request reveals if they work correctly.
  • Ignoring the `service_role` key – Even with perfect RLS, a leaked service_role key gives full access. Use a Vercel security scanner to check environment variables.
  • Overly permissive policies during development – It's easy to use USING (true) "for now" and forget to tighten it before launch.
  • Not checking policies after schema changes – Adding a column or renaming a table can break existing policies.
  • FAQ

    How do I run a Supabase RLS policy checker?

    You can run SQL queries in the Supabase SQL editor to list tables without RLS and review existing policies. For automated scanning, use a dedicated free AI app security scanner that connects to your Supabase project and flags issues.

    What does an RLS policy check look for?

    It checks for tables with RLS disabled, policies using overly permissive expressions (like true), missing policies for certain operations (e.g., INSERT without WITH CHECK), and potential logic errors in the policy's USING clause.

    Can I test RLS policies without deploying?

    Yes. Use the Supabase dashboard's SQL editor to run test queries as different roles. You can also use the supabase-js client with an anon key to simulate real user requests in a local or staging environment.

    Is your app secure?

    Free scan in 30 seconds. No signup needed.

    Scan My App Free