HOW TO SECURE SUPABASE - SECURITY GUIDE | VIBEEVAL

Critical: RLS is Required

Without Row Level Security enabled, anyone with your public anon key can read, modify, or delete every row in your database. The anon key is in your frontend bundle by design — what makes Supabase safe is the policy, not the key. RLS is not optional; it is the foundation of Supabase security.

Security Checklist

1. Enable RLS on every public table

ALTER TABLE my_table ENABLE ROW LEVEL SECURITY; — or in the dashboard: Database → Tables → [table] → Enable RLS. With RLS enabled but no policies, all access is blocked — you must add a policy in the same change to keep the app working. Verify with the Supabase RLS Checker.

2. Write specific RLS policies per operation

Don’t write USING (true) — that defeats the point. Per operation:

CREATE POLICY "users read own rows" ON profiles
  FOR SELECT USING (auth.uid() = user_id);

CREATE POLICY "users insert own rows" ON profiles
  FOR INSERT WITH CHECK (auth.uid() = user_id);

CREATE POLICY "users update own rows" ON profiles
  FOR UPDATE USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id);

Note USING (read filter) and WITH CHECK (write predicate) are both required for UPDATE — missing WITH CHECK lets users move rows to other users.

3. Test RLS policies with real keys

Don’t trust the dashboard’s “Policies look correct.” Test by querying with the anon key and a real Authorization: Bearer <jwt> header for two different test users. Confirm: user A sees only A’s rows, user B sees only B’s rows, neither can write to the other’s. The Supabase RLS Checker automates this.

4. Protect the service_role key

The service_role key bypasses RLS entirely. Never embed it in frontend code, never expose it via VITE_* / NEXT_PUBLIC_* / PUBLIC_* env vars, never commit it to git. Use it only in server-side functions / Edge Functions / your own backend. If exposed: rotate immediately at Settings → API → Reset service_role secret, then audit logs for the exposure window.

5. Understand anon-key safety

The anon key is meant to be public. With RLS configured, an anon key gives users access only to what your policies allow. Without RLS, the same key gives anyone everything. Search your bundle for the key string only as a sanity check — the real audit is the policy set.

6. Configure Supabase Auth correctly

Authentication → Providers → Email: enable “Confirm email” so users must verify before login. Authentication → Sign In/Up → Password requirements: minimum 8 chars + complexity. Authentication → URL Configuration → Site URL: set to your production domain only (no *). Authentication → Sessions: set inactivity timeout and absolute lifetime to your tolerance.

7. Configure Storage policies per bucket

Storage → [bucket] → Policies: same model as table RLS — enable + write a policy. Recurring shape: INSERT WITH CHECK (auth.uid()::text = (storage.foldername(name))[1]) to scope uploads to a per-user folder. Without a policy, the bucket is publicly writable.

8. Require SSL on database connections

In Settings → Database → Connection pooling: confirm SSL is required. For direct Postgres connections, append ?sslmode=require to the URL. For Supavisor-pooled connections, SSL is on by default — verify in the connection string.

9. Use database roles for service-side access

For your own server / function code that needs to bypass RLS for legitimate admin tasks: don’t use the global service_role; create a custom role with the minimum schema-level permissions and grant only the operations needed (GRANT SELECT, INSERT ON specific_table TO admin_role). Reduces blast radius if any individual key leaks.

10. Audit Edge Functions

Every Edge Function: confirm authentication is checked (Deno.env.get('SUPABASE_ANON_KEY') plus a verified JWT), inputs are validated, secrets come from Deno.env.get not hardcoded. The function’s response goes back to a client — don’t include internal state, file paths, or stack traces in errors.

11. Enable rate limiting on Auth endpoints

Authentication → Rate Limits: configure per-IP limits on /token, /signup, /recover, /verify. Defaults are generous; tighten to 10/min on auth endpoints. Without this, the auth endpoints are a free credential-stuffing surface.

12. Audit Realtime subscriptions

Database → Replication: only enable replication on tables that need realtime. Realtime subscriptions enforce RLS — but only if the publication includes the row. Per-row authorization is your responsibility through RLS; don’t enable replication on a table you haven’t policy’d.

13. Configure Point-in-Time Recovery

Settings → Database → Point in Time Recovery: enable for production projects (Pro plan and up). PITR is the only way to recover from a “user accidentally deleted everything” or “RLS misconfiguration deleted half the table” incident.

14. Enable audit logging

For Pro+ projects: Settings → Logs → Database: review weekly for: bulk reads from the anon key, repeated 4xx auth errors, queries to tables you don’t expect frontend traffic on. Anomalies are usually a sign someone is probing.

15. Run an automated RLS scan

The Supabase RLS Checker probes every public table and reports which return rows without auth — catches the “RLS forgotten on a new table” regression that ships every time AI adds a new feature. Schedule it as a CI step on every deploy.

Common Vulnerabilities in Supabase Apps

Tables Without RLS

The default for newly-created tables is RLS off. AI tools (Lovable, Bolt, Cursor) frequently create tables mid-conversation and don’t enable RLS — see the RLS misconfiguration atlas.

USING (true) Policies

Enabling RLS but writing USING (true) is the same as no RLS. Common when an AI tool “fixes” a missing-RLS warning by adding a permissive policy.

Self-Editable Role Fields

A profiles table with an is_admin column and an UPDATE policy that allows any field — the user PATCHes {is_admin: true} and is now admin. Strip role-related columns from the policy or use a trigger.

service_role Key in Frontend

A VITE_SUPABASE_SERVICE_ROLE_KEY env var ships to the browser. Bypasses every policy. Rotate immediately on discovery.

Supabase RLS Misconfiguration Atlas

The recurring patterns we see across AI-built Supabase apps.

Free Self-Audit Suite

Five free scanners.

Vibe Coding Security Risk Guide

Full risk catalogue.

Test Your RLS Policies Automatically

VibeEval probes every public Supabase table with the anon key and reports which return data — and where the policy logic has gaps. Findings ship with paste-ready SQL fixes.

SCAN YOUR APP

14-day trial. No card. Results in under 60 seconds.

START FREE SCAN