IS SUPABASE SAFE? SUPABASE SECURITY REVIEW 2026 | VIBEEVAL

RLS is Non-Negotiable

Supabase exposes your PostgreSQL database directly to clients via the anon key. Without RLS policies, anyone with your project URL can read, modify, or delete all data in unprotected tables. The anon key is not a secret — it ships in the JavaScript bundle of every Supabase app on purpose. That design choice is fine when RLS is enforced. When RLS is not enforced, every table is a public API.

The pattern we see most often: AI generators scaffold a Supabase project, create five tables, and write the frontend that reads from them. They never run ALTER TABLE x ENABLE ROW LEVEL SECURITY. The app works in dev because the dev project has one user. It works in prod for one user. It works for two users — until user B types user A’s UUID into the URL bar.

Common Security Issues

Missing RLS Policies

Tables without RLS enabled are fully accessible to anyone with the anon key, leading to complete data exposure. Check with:

SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public' AND rowsecurity = false;

Anything in that result is publicly readable through the Supabase REST or GraphQL endpoint.

Service Role Key Leaks

The service_role key bypasses RLS. Exposing it in client code grants full database access to attackers. Common leak paths: pasted into a .env.local that ships in the Next.js bundle (NEXT_PUBLIC_* variables), committed to a public repo “just for the demo”, logged at server boot, or returned in an error response from an Edge Function.

Rotate immediately if you suspect a leak — and after rotation, audit pg_stat_statements for queries that wouldn’t have come from your app.

Flawed RLS Policies

RLS policies with logical errors create unintended access paths. The most common mistake is the “permissive policy that allows everything”:

-- BAD: this is the same as no RLS
CREATE POLICY "users can read" ON profiles
  FOR SELECT USING (true);

-- GOOD: scope to the authenticated user
CREATE POLICY "users read own profile" ON profiles
  FOR SELECT USING (auth.uid() = user_id);

Equally dangerous is the policy that uses auth.uid() only on SELECT and forgets UPDATE/DELETE/INSERT. Each command needs its own policy unless you use FOR ALL.

Storage Bucket Misconfigurations

Supabase Storage also requires RLS. Public buckets may expose sensitive files. The standard mistake is making a bucket public so the CDN works, then storing per-tenant files in it with predictable paths like /uploads/{user_id}/{file}.pdf. Anyone who guesses a UUID gets the file.

For private buckets, write a Storage policy:

CREATE POLICY "users read own files"
ON storage.objects FOR SELECT
USING (auth.uid()::text = (storage.foldername(name))[1]);

JWT Secret Reuse Across Projects

Supabase signs JWTs with a per-project secret. If you copy the secret across staging and prod (or share it across two unrelated apps), a token issued in one environment is valid in the other. Always treat the JWT secret as a per-environment value.

Unprotected Database Functions

SECURITY DEFINER functions run with the privileges of the function owner — usually a superuser. A function that takes a user_id parameter and reads from any table without checking the caller is a complete RLS bypass. Audit every SECURITY DEFINER function and confirm it validates auth.uid() against its inputs.

Security Assessment

Strengths

    • PostgreSQL with enterprise-grade security
    • Row Level Security (RLS) for fine-grained access
    • Built-in authentication with JWT tokens
    • Open source - security auditable
    • SOC 2 Type II compliance
    • Encryption at rest and in transit by default
    • Per-project JWT signing keys

Concerns

    • RLS policies often missing or misconfigured
    • Default settings may expose data
    • Anon key in client code - RLS is essential
    • Service role key leaks grant full access
    • Complex RLS syntax leads to security gaps
    • SECURITY DEFINER functions can bypass RLS silently
    • Storage buckets need their own policies, easy to forget

Supabase vs Firebase: a quick model comparison

Both platforms put a database directly behind a public key and rely on declarative rules to keep data scoped. The differences:

  • Rule language. Supabase uses SQL via CREATE POLICY. Firebase uses its own Security Rules DSL. SQL is more powerful and composable; the DSL is more compact for simple per-document rules.
  • Default after enable. RLS enabled with no policy = deny all. Firebase rules with no match block = deny all. Both fail safely if you stop halfway. Both fail unsafely if you write a permissive rule to “make it work”.
  • Auth integration. Both ship first-party auth that injects a user ID into rules. Supabase exposes auth.uid(); Firebase exposes request.auth.uid.
  • Server escape hatch. Supabase has service_role. Firebase has the Admin SDK. Both bypass all rules. Both are the source of most catastrophic leaks.

The Verdict

Supabase is safe as a platform with PostgreSQL’s battle-tested security. The critical factor is proper RLS configuration. Enable RLS on every table, write and test policies thoroughly, and never expose the service_role key in client code. With proper configuration, Supabase provides excellent security.

The four checks before production:

  1. Every table in the public schema has rowsecurity = true.
  2. Every table has at least one policy per command (SELECT, INSERT, UPDATE, DELETE) that references auth.uid().
  3. The service_role key exists only in trusted server environments — grep your repo and your client bundle for it.
  4. Storage buckets are private unless they are genuinely meant for the public CDN, and private buckets have policies that scope by user.

How to Secure Supabase

Step-by-step security guide covering RLS rollout, policy testing patterns, and Storage bucket lockdown.

Supabase Security Checklist

Interactive security checklist with the SQL queries to confirm each item.

Supabase RLS Checker

Run an automated scan against your live Supabase project that probes every table for RLS bypass and reports any rows the anon key can reach.

Token Leak Checker

Find the service_role key (and other secrets) that may have shipped to your browser bundle.

Scan Your Supabase App

Let VibeEval check your Supabase application for RLS misconfigurations and vulnerabilities. The scanner walks every public route, exercises the anon key against your tables, and reports the rows it should not have been able to read.

COMMON QUESTIONS

01
Is Supabase safe to use in production?
Yes, when you treat the anon key as public and protect every table with Row Level Security. The platform itself is SOC 2 Type II compliant and runs on managed PostgreSQL. The risk is not in the platform — it is in tables that ship with RLS disabled or with policies that read like access controls but actually allow anything.
Q&A
02
Why is the Supabase anon key not really anonymous?
The anon key is a JWT signed by your project that gets embedded in your browser bundle. Anyone visiting your site has it. It is the equivalent of publishing a database connection string — except the only thing standing between that key and your data is RLS. If RLS is off, the anon key reads everything.
Q&A
03
Does enabling RLS on a table block all access?
Yes. Enabling RLS without writing any policy denies all reads and writes from the anon role. This is the safe default. The mistake AI generators make is enabling RLS and then immediately writing a permissive policy like `USING (true)` to make the app work, which is the same as not having RLS at all.
Q&A
04
What is the difference between anon, authenticated, and service_role keys?
anon is the public key in the browser. authenticated is the same key after a user signs in — RLS policies can reference `auth.uid()` to scope rows. service_role is a god-mode key that bypasses RLS entirely. service_role belongs only on a trusted server, never in client code or a public repo.
Q&A
05
Are Supabase Storage buckets covered by RLS?
Yes — Storage uses the same RLS engine. A public bucket means anyone can read every object. A bucket with RLS but no policies denies reads. Most leaks happen with buckets marked public for ease of CDN access without considering that file paths often encode tenant IDs.
Q&A
06
Can Supabase Edge Functions leak the service_role key?
Edge Functions run server-side and have access to environment secrets, including service_role. The leak happens when developers log the env at startup, return error messages that include connection details, or accidentally expose the function source map. Treat any Edge Function with service_role as a privileged service.
Q&A
07
How does Supabase compare to Firebase for security?
Both expose data directly to clients and rely on declarative rules. Supabase uses PostgreSQL RLS, which is strict-deny by default once enabled. Firebase Security Rules are deny-by-default but use a custom rule language. RLS gives you access to the full Postgres role and policy system; Firebase gives you tighter integration with Auth. Both fail the same way: rules that nominally allow access without actually checking the user.
Q&A

SCAN YOUR APP

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

START FREE SCAN