Guide
Securing Supabase: Fixing RLS and Key Leaks in Replit
Most Lovable, Bolt, and v0 stacks lean on Supabase, but models ship policies and keys that belong in a tutorial, not production. Here is how that surfaces on the public web, what we check from your live URL, and how to steer toward a hardened posture.
How Supabase leaks through the browser
Your AI builder wired up createClient with a project URL and anon key. Both ship to every visitor. That is normal. What is not normal is treating RLS as optional: without tight policies, PostgREST exposes tables as if they were a public REST API.
Worse, generators sometimes drop service_role or other secrets into client env so a feature “unblocks.” Those keys bypass RLS entirely. Attackers do not need your password; they need the same JavaScript bundle your users already downloaded.
Our scan is passive: we look at what the open web can see (URLs, bundles, config patterns), then map how that pairs with common Supabase foot-guns so you can fix RLS and key placement before someone else runs .select('*') against your project.
What our scan flags (Supabase surface)
Short version of the patterns we correlate from your public app URL. Your email report spells out severity and what to fix first, usually policies, key rotation, or moving secrets server-side.
service_role in the client
Typical AI-builder slip: The model inlined the Postgres super-key so the UI "just works"; PostgREST will obey it.
CRITICAL (Full DB access)RLS missing or always-true
Typical AI-builder slip: Table is exposed to anon/authenticated but policies are empty, permissive, or never added.
HIGH (Bulk data theft)Anon key + URL in the bundle
Typical AI-builder slip: SUPABASE_URL and SUPABASE_ANON_KEY ship in every build; without RLS that pair is a public read/write API.
HIGH (Automated scraping)Auth session ≠ row ownership
Typical AI-builder slip: JWT or session exists but policies don’t tie reads/writes to auth.uid(), so users hit each other’s rows.
MEDIUM (Cross-tenant leak)
Hardened Supabase vs leaky Supabase
Same Supabase project type, opposite posture: where RLS and keys are enforced vs where AI-generated glue leaves you one fetch away from disaster.
Row Level Security
Hardened
Every table with PII or tenant data has explicit policies; anon can’t SELECT/UPDATE what they shouldn’t.
Leaky
RLS never enabled, or policies use USING (true) so any key that reaches the browser can read everything.
Keys & Supabase client
Hardened
Only anon + URL in the frontend; service_role and admin keys live in server-only env or edge functions.
Leaky
service_role or bypass JWT copied into Vite/Next public env, or fetched from a mis-cached edge route.
Policies vs auth
Hardened
Policies reference auth.uid() / JWT claims so each request only sees its tenant’s rows.
Leaky
Client filters in React but PostgREST still returns full tables. UI hiding is not security.
Ship pipeline
Hardened
Preview projects don’t point at prod; maps and bundles don’t embed prod secrets.
Leaky
Lovable/Bolt/v0 preview URLs hit prod Supabase; wrong NEXT_PUBLIC_ / import.meta.env leaks prod.
If you ignore RLS and key hygiene
Supabase is fast to ship, until the wrong key or policy turns your database into someone else’s spreadsheet.
Your Postgres rows are a public API
Anyone who replays the anon key, or steals a leaked service_role, can read and often write data without touching your UI. Supabase will happily execute what the policy allows.
RLS gaps block enterprise
Buyers and auditors ask how row-level enforcement works. “We’ll add policies later” fails SOC 2, GDPR reviews, and security questionnaires.
Key rotation doesn’t undo exfil
Once data is scraped, rotating keys stops new abuse but not stolen rows. Incidents are expensive; tightening RLS and key hygiene up front is cheap.
Why AI builders ship unsafe Supabase by default
Generators optimize for a green checkbox in the preview, not a policy review. They add tables, enable realtime, wire supabase-js, and move on. RLS becomes a comment for later. With only the anon key, that is fine only if every policy is bulletproof; it almost never is on the first pass.
Tools also love serverless and edge snippets that blur where a secret lives. One bad env prefix (public vs server) and your service_role is in the same chunk as your components. Multiplied across Lovable, Bolt, and v0 deploy pipelines, that is how production projects end up teaching attackers your schema.
This scan does not replace a full pentest or a manual policy audit. It is the gut check that catches the catastrophic misconfig, the one you can fix tonight before you scale traffic. For a structured path through auth, RLS, and safe client boundaries, VibeMastery walks the fixes step by step.
