6 Supabase RLS Misconfigs in 90% of Vibe-Coded Apps

6 Supabase RLS Misconfigs in 90% of Vibe-Coded Apps
Published

April 17, 2026

Last updated

April 30, 2026

Public scans show 11% of vibe-coded apps leak Supabase keys. Here are the 6 RLS misconfigs behind 90% of them, with real SQL examples and the fixes.

When we audit a Supabase backend an AI coder generated, the same six Row Level Security failures show up over and over. Public scanners agree. SupaExplorer's January 2026 sweep of 20,000+ indie launch URLs found 11% leaking Supabase credentials in their frontend bundle. A 2026 audit of five major AI app builders flagged sensitive-data exposure in over 40% of apps. CVE-2025-48757 alone covered 170 vulnerable showcased apps. Here's what we keep finding.

Is RLS actually turned on?

The AI generated CREATE TABLE statements and skipped ALTER TABLE … ENABLE ROW LEVEL SECURITY. PostgREST then serves every row to anyone holding the public anon key. One independent scan of 50 AI-generated apps in early 2026 found 89% had at least one fully exposed table.

CREATE TABLE public.user_profiles (
  id uuid PRIMARY KEY,
  user_id uuid REFERENCES auth.users,
  email text,
  stripe_customer_id text
);
-- No ALTER TABLE ... ENABLE ROW LEVEL SECURITY. None.

Does your policy actually scope to the user?

This is RLS-in-name-only. USING (auth.role() = 'authenticated') says "if you're logged in, read all rows," and that includes every other user's data. Supabase's Security Advisor flags it (rule 0024). Most teams see a green "RLS Enabled" badge in the dashboard and call it done.

-- Looks fine in the dashboard but it isn't
CREATE POLICY "users can read" ON public.user_data
  FOR SELECT USING (auth.role() = 'authenticated');

-- What it should be:
CREATE POLICY "users can read" ON public.user_data
  FOR SELECT TO authenticated
  USING ((select auth.uid()) = user_id);

Is the service_role key in your browser bundle?

Environment variables get crossed and SUPABASE_SERVICE_ROLE_KEY ships as VITE_* or NEXT_PUBLIC_*. Service role bypasses RLS entirely. Full read/write across every table. The Moltbook incident on January 31, 2026 leaked 1.5M API tokens via this exact mistake. The Supabase MCP variant, where coding agents hold service_role and ingest untrusted user input, was demonstrated in mid-2025 and remains an open class of risk.

Can users update columns they shouldn't?

SELECT is correctly scoped to auth.uid(). Then the UPDATE policy lets users modify their own row, including columns like role or subscription_tier. The 18,697-user breach disclosed February 27, 2026 traced to inverted policy logic produced by an AI optimizing for "code that runs."

-- Missing WITH CHECK = users can flip their own role to admin
CREATE POLICY "update own profile" ON public.profiles
  FOR UPDATE TO authenticated
  USING ((select auth.uid()) = user_id);
-- Add WITH CHECK to enforce ownership on write,
-- then revoke UPDATE on the role column at the table level.

Are your views silently bypassing RLS?

Postgres views run with the privileges of their owner, usually postgres. AI-generated migrations expose convenience views like user_dashboard_data without security_invoker = on, and PostgREST serves them to anyone with the anon key. Same risk for SECURITY DEFINER functions sitting in the public schema.

ALTER VIEW public.user_dashboard_data
  SET (security_invoker = true);

Are storage buckets actually locked down?

Buckets default to convenient over correct. We see public buckets with sensitive uploads, or private buckets with USING (true) policies. User-folder isolation via (storage.foldername(name))[1] = (select auth.uid())::text is almost never present on a first audit.


When all six show up in one project, which is the median case and not the worst, fixing them is roughly a week of work. Shipping with them is roughly the cost of a CVE.