Security & Privacy

Is My Supabase Database Publicly Accessible?

J
James Eriksson
··12 min read
Check if your Supabase database is publicly accessible. Simple 60-second self-check. Learn why RLS matters, how to verify policies work, and fix exposure immediately.
TL;DR
  • Your Supabase database is publicly readable if RLS is disabled or has zero policies. Check this in 60 seconds using the self-check above.
  • The 170+ Lovable apps exposed in January 2025 all had RLS disabled by default. Lovable and Supabase now warn you, but the mistake is still easy.
  • RLS disabled = anyone with your anon_key can read all rows. RLS enabled with no policies = access denied (app breaks). RLS with working policies = only authorized rows are visible.
  • Test your policies from client code with the anon_key, not from the SQL Editor (which uses the admin key and bypasses RLS).
  • If exposed, enable RLS, regenerate keys, write policies, and notify users.

Your Supabase database is publicly readable if you have tables in the public schema without Row Level Security (RLS) enabled. You can check this in 60 seconds. This guide walks you through the diagnosis, explains why it happens, and shows you exactly how to fix it.

Is Your Supabase Database Publicly Accessible? The 60-Second Self-Check

Copy this checklist. Stop at the first no.

  1. Do you have RLS enabled on all tables? Go to Authentication > Policies and toggle it on for each table.
  2. Does each table have at least one policy? A table with RLS enabled but zero policies blocks all access (your app breaks). A table with RLS disabled allows unrestricted access (data is exposed).
  3. Is your service_role key only in backend environment variables? Not in frontend code, not in GitHub, not in.env files.
  4. Did you test your policies by logging in as a different user and trying to read rows you should not see? The SQL Editor uses the service_role key by default, so it does not show you if RLS is actually working.

If all four are true, your database is private. If any is false, keep reading.

Why This Matters: How 170+ Lovable Apps Got Exposed

In January 2025, security researchers discovered 170+ Lovable-generated applications with exposed Supabase databases. The root cause was not a vulnerability in Lovable or Supabase. It was a framework default: when Lovable generates code, it creates tables without RLS enabled. Developers deployed to production without checking this setting. Supabase now offers a free security check, and Lovable has added built-in warnings, but the default is still to create tables without RLS.

Your app faces the exact same risk if you have not explicitly enabled RLS and written policies. The danger is silent. No error message appears. No log entry records that data is exposed. You have to check manually. This is why hundreds of apps have been exposed by accident.

How Supabase Exposure Actually Works: The Role of RLS

Supabase auto-generates a REST API for every table in the public schema of your project. Any client with your anon_key (the public, frontend-facing key) can call this API. The anon_key is not secret, it is in your frontend code by design. So by default, anyone who finds your project can list, insert, update, or delete rows in any public table that lacks RLS.

Row Level Security (RLS) is a Postgres database-level rule that restricts which rows a user can access. When you enable RLS on a table, Supabase enforces the policy at the database, not at the API layer. Even if someone has your anon_key, they can only see rows their policy allows.

Without RLS: your anon_key grants full unrestricted table access. With RLS and no policies: access is denied to everyone (your app breaks). With RLS and a working policy like "users can only see rows where user_id = auth.uid()": access is row-specific and private.

Most exposure happens because developers forget step one: enabling RLS. Step two (writing policies) is harder, so more bugs hide there.

The Four Common Mistakes That Expose Data

Mistake 1: RLS Disabled (Silent Exposure)

RLS is disabled by default when you create a Supabase project. This is intentional (it is simpler to start with no restrictions), but it is a security footgun. Your table is fully readable and writable via the anon_key. Most developers do not realize this is the state they are in until production.

You think you are building with a secure backend. You are not. You are building with a public database that anyone can read.

Fix: Go to the Supabase dashboard. Click Authentication > Policies. Find your table. Toggle "Enable RLS" on. Test immediately (see the verification section below). Your app may break at this point (if you have not written policies), and that is good: it means RLS is working and you must write policies before re-enabling traffic.

Mistake 2: RLS Enabled With No Policies (Silent Lockout)

You see the RLS toggle and turn it on. You feel secure. But you did not write any policies. Result: the anon_key gets zero rows from every query. Your app silently fails to load data. Users see blank screens.

This happens because RLS defaults to deny-all when enabled with zero policies. This is safer than defaulting to allow-all, but it is not obvious to developers. Many think enabling RLS is the security step, not writing the policies.

You are not exposed, but you are broken.

Fix: Write at least one policy per table. Start simple: CREATE POLICY "users can read their own rows" ON [table] FOR SELECT USING (auth.uid() = user_id);. Test this works from client code (not the SQL Editor).

Mistake 3: service_role Key in Client Code (Key Theft)

The service_role key is a superuser key. It bypasses all RLS policies and grants full database access. If you put it in your frontend code or commit it to GitHub (even by accident), an attacker has unrestricted access to your database.

This is the fastest data-exposure vector. It is not a misconfiguration of RLS, it is a leak of the master key.

How it happens: You test locally with the service_role key in your.env file. You forget to use the anon_key in frontend code. You push to production with the service_role key in your bundle or Docker image. An attacker extracts it. Game over.

Fix: service_role key lives in backend environment variables only (e.g., SUPABASE_SERVICE_ROLE_KEY=...). Never in frontend code. Never in.env files checked into Git. If you think you exposed it, regenerate it immediately in the Supabase dashboard (Settings > API).

Mistake 4: RLS Without Indexes (Performance Disaster)

RLS policies are Postgres queries. If your policy scans a large table (e.g., SELECT * FROM users WHERE user_id IN (SELECT user_id FROM user_groups WHERE team_id = auth.user_metadata ->> 'team_id')), without an index, every query becomes slow. At scale, queries timeout.

This is not a security gap. But it forces developers to disable RLS to restore speed, which re-exposes your data.

Fix: Index the columns your policies filter on. If your policy is WHERE user_id = auth.uid(), run CREATE INDEX idx_users_user_id ON users(user_id);. If your policy uses a JOIN, index the join column too.

How to Actually Verify Your Policies Work: Testing That Doesn't Lie

The Supabase SQL Editor uses the service_role key by default. This means RLS does not apply when you test in the editor. If you run SELECT * FROM users in the SQL Editor, you get all users, even if your policy restricts access to the logged-in user only. You feel secure but have tested the wrong thing.

To verify policies actually work: test from your client code using the anon_key.

  1. Create a test route in your app (or open the browser console if your app is a SPA) that queries a protected table with the anon_key.
  2. Log in as User A. Query rows owned by User B. You should get zero rows or an error.
  3. Query rows owned by User A. You should get your own rows.
  4. Log out (or switch to a different user account). Query again with the anon_key. You should get an error or be denied.

If step 2 returns rows you should not see, or step 4 returns data without authentication, your policies are not working.

Bonus check: Go back to the Supabase dashboard. Table > Authentication > Policies. Confirm RLS is toggled on (not just enabled in your mind). Make sure the policy you wrote is actually saved.

If You Are Already Exposed: Immediate and Long-Term Fixes

Immediate (Next 24 Hours)

  1. Enable RLS on all tables. Go to Authentication > Policies. Toggle it on for each table.
  2. Assume your data has been read. If you store passwords, credit card numbers, API keys, or PII, treat this as a breach. Notify your users. Check if there were unauthorized logins or unusual account activity.
  3. Regenerate your service_role key. Go to Settings > API. Generate a new one. This invalidates any copied key.
  4. Scan GitHub and Docker images for any exposed keys. Use GitGuardian, GitHub secret scanning, or grep for your key values. Rotate anything that was exposed.

This Week

  1. Write and test RLS policies for each table. Test from client code, not the SQL Editor.
  2. Add indexes to columns your policies filter on (see Mistake 4).
  3. Do a second verification round: log in as different users and confirm that row isolation works.
  4. If you had paying customers during exposure, send a security notice with what data was exposed, when, and what you fixed.

Longer-Term

  1. Enable audit logging. Go to Settings > Logging. Review logs monthly for unusual queries.
  2. Add a policy review to your sprint retro. "Are our RLS policies still correct?" every two weeks.
  3. Consider managed hosting like Ship. It removes the RLS configuration burden and verifies policies before deployment.

The Production Security Checklist

Use this before your first paying customer:

  • RLS is enabled on all tables in the public schema (not just tables you think users see)
  • Every table with RLS has at least one policy (not zero policies, which blocks access silently)
  • You tested each policy by logging in as different users and confirming row isolation
  • You ran a GET request against a protected table with the anon_key and confirmed the policy worked
  • Service_role key is not in your frontend code, GitHub,.env files, or Docker images
  • All columns referenced in your policies have indexes (CREATE INDEX idx_table_column ON table(column))
  • Storage buckets, if used, have RLS enabled and policies defined
  • Audit logging is enabled and you know how to review logs
  • You have a runbook for "what to do if exposed" (which keys to rotate, who to notify, etc.)
  • You ran the Supabase security check or supabase-security-checker tool against your project

Moving Beyond DIY: When to Reach for Managed Hosting

RLS configuration is correct and critical. Mistakes are easy. Testing is tedious. Forgetting a single policy in one table re-exposes your data. Many teams get it right the first time, but more get exposed later when they add a new feature and forget to add the policy to the new table.

If database security is becoming a recurring point of friction in your team (policy reviews before every deployment, questions about whether the anon_key should be client-side, auditing tables for RLS), managed hosting like Ship handles RLS configuration and verification by default. You deploy your app, and the database is secure out of the box. No policy writing required unless your security model is custom.

This is not a shortcut around learning RLS. You should still understand it. But it is a pragmatic off-ramp if you are rebuilding policies after security reviews or if you want infrastructure that enforces safety before you ship.

Learn more about deploying and hosting Lovable apps on Ship, which includes a pre-flight security check before any production deployment.

Frequently Asked Questions

Are Supabase databases public?

Not by default, but yes if RLS is disabled. Supabase databases are private once RLS is enabled and at least one policy is written. Without RLS, anyone with your anon_key can read and modify all rows in public tables.

Does Supabase have access to my data?

Supabase can access your data using the service_role key, which is a backup administration key. Your data is encrypted at rest and in transit per Supabase docs. If you are in the EU, data is stored in Frankfurt by default. Data access is logged if audit logging is enabled.

What are the disadvantages of Supabase?

RLS has a steep learning curve and requires SQL knowledge. Policies are written in SQL, not a UI. Vendor lock-in is real (Postgres flavor, Supabase-specific APIs). Free tier limits (500MB storage, 2GB bandwidth). Scaling beyond their managed tier requires self-hosting or migration.

How do I see my Supabase database?

Log in to the Supabase dashboard, click your project name, go to the SQL Editor or Table Editor. The SQL Editor runs raw SQL queries (with service_role access). The Table Editor shows a spreadsheet view of your tables (also with service_role access, so RLS is not applied here).

How do I check if my Supabase database is public?

Use the 60-second self-check at the top of this post. Or query your Supabase API endpoint with your anon_key and try to list a table. If you get rows back, it is public. For example, you could test with your API credentials to confirm the database is accessible without authentication.

Can Supabase be hosted locally?

Yes. For development: use the Supabase CLI (docker compose) to spin up a local Supabase instance. For production: Supabase Docker images are available, but orchestration is complex (PostgreSQL, GoTrue auth, pg_graphql, vector search all need to run). Most teams use managed Supabase or an alternative like Ship for production.

The Bottom Line

Your Supabase database is exposed if RLS is disabled or if RLS is enabled with zero policies. The exposure is silent. No alarm goes off. You must check manually. Use the 60-second checklist, test your policies from client code (not the SQL Editor), and enable RLS on every table before production. If you deployed without RLS, treat it as a security incident: regenerate your keys, enable RLS, write policies, and notify users if you stored sensitive data.

RLS is not optional. It is the foundation of Supabase security.

If database security is becoming a persistent headache for your team, check out Ship for secure app hosting with RLS verification built in.

Secure your app before launch
Ship includes a pre-flight database security check for all deployed apps.
Get Started Free

Ready to self-host your own apps?

One server. Multiple apps. No per-app fees.

Get started →