Key Takeaways
- Most Supabase data leaks come from a missed check, not a sophisticated attack. New tables ship with RLS off by default.
- The
anonkey is meant to be public and is safe to expose only when RLS is correct. Theservice_rolekey bypasses RLS and must never reach the browser. - Views and
SECURITY DEFINERfunctions can ignore RLS. Review every client-facing view and function, and preferSECURITY INVOKER. - Enabling RLS with no policy denies all access, which is the safe starting point. Then add explicit, scoped policies with a
WITH CHECKclause. - Test both allowed and denied paths with two separate users before launch, and re-run the checklist after every schema change.
Why a Checklist Beats a Clever Defence
Supabase is a joy to build on because it removes boilerplate, but that same speed hides security decisions behind sensible-looking defaults. The result, seen again and again in 2026, is that the typical Supabase incident is not an exotic exploit, it is a table that shipped with row-level security disabled, or a service_role key that ended up in a frontend bundle. Those are checkbox failures, and a checkbox is exactly how you prevent them.
Supabase publishes its own production checklist, and you should run it. The list below complements it with the database-layer checks an attacker actually probes: not just whether RLS is on, but whether your policies, views, functions and grants hold up when someone calls the API directly instead of using your app.
Checks 1 to 3: Row-Level Security Fundamentals
Row-level security is the foundation, and it has to be deliberate on every table. Remember that CREATE TABLE leaves RLS off, so any table you added quickly, or a collaborator added, may be wide open to the anon key. Enabling RLS with no policies is the safe default: it denies everything until you add explicit grants.
- RLS enabled on every exposed table. Check the Supabase dashboard or
pg_class.relrowsecurity. A single table without RLS in a client-exposed schema is readable and writable by anyone with the anon key. - Every table has explicit, scoped policies. Scope reads and writes to the authenticated user's own data, for example
(select auth.uid()) = user_id, never a blankettrue. - Every UPDATE, INSERT and ALL policy has a WITH CHECK clause. Without it, the new row is not validated and no column is protected, so a user can rewrite columns you never intended.
Worried Your Supabase RLS Has These Gaps?
Codesecure audits Supabase and PostgreSQL row-level security the way an attacker tests it: every policy, every SECURITY DEFINER function, every anon and authenticated grant. We deliver a prioritised findings report with exact remediation SQL. ISO/IEC 27001:2022 certified delivery, named OSCP and CEH consultants, fixed-price proposals, for businesses across India, Singapore, UAE and Malaysia.
See Database Security Services →Checks 4 to 5: The Two Keys That Decide Everything
Supabase gives you two keys, and confusing them is behind many leaks. The anon key is designed to live in your frontend and is safe to expose precisely because RLS stands between it and your data. The service_role key is the opposite: it bypasses RLS entirely and is effectively a master key, so it belongs only on a trusted server.
- The service_role key is server-side only. Grep your client bundle, environment variables and git history for it. If it has ever shipped to a browser or a public repo, rotate it now.
- The anon key is protected by RLS, not by secrecy. Treat it as public, and make sure RLS is correct so that being public is safe. If removing RLS would expose data to the anon key, that data is one mistake away from exposure.
Checks 6 to 7: Views and Functions That Ignore RLS
Two database objects quietly sidestep row-level security, and both are easy to miss because the table underneath looks locked down.
- Review every client-facing view. Views can run with the view owner's rights and bypass the RLS on their underlying tables. Recreate client-facing views with
security_invoker = trueso the caller's policies apply. - Audit every SECURITY DEFINER function. They run as the owner and ignore RLS, and Supabase exposes them at
/rest/v1/rpc/with EXECUTE granted to PUBLIC by default. Revoke EXECUTE from anon and public, authoriseauth.uid()inside the body, and preferSECURITY INVOKERfor read-only functions.
Checks 8 to 10: Grants, Storage and Secrets
RLS is one layer. The grants, storage policies and secret handling around it decide your blast radius when something else slips.
- Least-privilege table grants. By default roles can be granted broad privileges. Revoke
TRUNCATE,REFERENCESandTRIGGER, and any DML, that anon and authenticated do not need, so a single dropped policy does not expose full CRUD. - Storage bucket policies. Object storage needs the same owner checks as tables. Confirm private buckets are not readable or writable by anon, and that signed URLs are scoped and time-limited.
- No secrets in readable columns. SMTP passwords, API keys and tokens belong in Supabase Vault or an encrypted, write-only field, never in a column that is returned to the client or captured in backups.
Not Sure If Your Policies Are Safe?
Send us your Supabase project and we will scope a row-level security review on a free 30-minute call. We assess RLS coverage, column exposure, RPC bypass paths and tenant isolation, then hand you the fixes.
Book a Free Scoping Call →Check 11: Test Allowed and Denied Paths With Two Users
Policies are code, and untested code is a guess. The single most valuable pre-launch step is to actually exercise your access rules. Create two test accounts in different tenants and, for every sensitive table and RPC, confirm that the allowed path works and, more importantly, that the denied path fails: user A must not read, update or delete user B's rows, and the anon role must not reach anything it should not.
Run these tests against the REST API and the RPC endpoints directly, not through your UI, because an attacker will. This is also where a professional review adds the most value: we script these cross-tenant and anonymous tests across your whole schema so no table is assumed safe just because the dashboard says RLS is on.
Check 12: Harden the Account, Then Re-Check
Finally, protect the project itself and keep the checklist alive. Enable multi-factor authentication on your Supabase account, review who has access to the project, and use the built-in Security Advisor to catch obvious database issues. Then treat this list as a recurring gate: because RLS is off by default on new tables and new functions default to broad EXECUTE, every schema change can reopen a gap you already closed.
Bake the checks into your deployment process so a new table or function cannot reach production without passing them. Security that is a one-time launch task decays with the next migration; security that is a repeatable check holds.
Frequently Asked Questions
Is Supabase secure by default?
Supabase gives you strong security tools, but the defaults are not fully locked down. New tables have row-level security disabled until you enable it, functions default to broad EXECUTE, and views can bypass RLS. Supabase is secure when you configure RLS on every exposed table, write scoped policies, keep the service_role key server-side, and audit views and functions. The platform provides the controls; you have to turn them on.
Is it safe to expose the Supabase anon key in frontend code?
Yes, the anon key is designed to live in your frontend, but only if row-level security is correctly configured on every table it can reach. RLS is what stands between the public anon key and your data. If a table has RLS disabled or an overly permissive policy, the anon key becomes a master key to that table. So the anon key is safe to expose precisely to the extent that your RLS is correct.
What is the difference between the Supabase anon key and the service_role key?
The anon key is a public key for your frontend and is fully subject to row-level security. The service_role key bypasses RLS entirely and acts as a superuser over your data, so it must only ever be used on a trusted server and must never appear in client code, environment variables shipped to the browser, or a public repository. If the service_role key leaks, rotate it immediately.
Do I need RLS on every table in Supabase?
On every table exposed through the API, yes. Any table in a client-accessible schema without row-level security is readable and writable by anyone holding the anon key. Enable RLS on every such table, and remember that new tables ship with RLS off, so this is a check to repeat after each migration, not a one-time task.
How do I test my Supabase RLS before launch?
Create two test users in different tenants and, for each sensitive table and RPC, verify the allowed path works and the denied path fails: user A must not access user B's rows, and the anon role must not reach protected data. Run the tests directly against the REST and RPC endpoints, not through your UI, because that is how an attacker reaches them.
Can Codesecure review our Supabase project before we go to production?
Yes. Codesecure runs a pre-production Supabase security review covering RLS coverage, policy scoping and WITH CHECK, anon and service_role key handling, views, SECURITY DEFINER functions, grants, storage and secrets, and we script cross-tenant and anonymous tests across your schema. You get a prioritised report with the exact fixes. ISO/IEC 27001:2022 certified delivery with named OSCP, CEH and CISSP consultants.
Ship Your Supabase App Without the Leak
Codesecure runs a pre-production Supabase review that tests RLS, keys, views, functions, grants and storage the way an attacker would, and hands you the exact fixes before you go live. ISO/IEC 27001:2022 certified delivery by named OSCP, CEH and CISSP consultants across India, Singapore, UAE and Malaysia.

