· via dev.to (home feed)
Seven ways Supabase apps leak data even with Row Level Security enabled
A dev.to post documents seven ways Supabase apps leak data despite Row Level Security, from permissive policies and missing WITH CHECK clauses to views, grants, storage buckets, Realtime and SECURITY DEFINER functions.

A post on dev.to catalogs seven concrete ways a Supabase application can expose one user's data to another even after Row Level Security (RLS) has been enabled. The author's core argument is that the green "RLS enabled" indicator in the dashboard breeds false confidence: enabling RLS only makes a table participate in the mechanism, while policies, views, grants and surrounding features still decide who actually sees what.
According to the post, the typical scenario is a developer who turns RLS on, writes one policy, tests with their own account, and only discovers the gap when a user reports seeing someone else's invoice.
Seven surfaces where data still leaks
Effectively open policies. A table with RLS enabled and no policy at all blocks everyone except the service role — broken, but not a leak. The dangerous case is a permissive policy, frequently a leftover development rule such as
for select using (true), or one granted to thepublicoranonrole. Because the anon key is embedded in the client bundle and visible to anyone, such a policy makes the table world-readable. The suggested fix is to scope reads by ownership, for exampleauth.uid() = user_id.Missing WITH CHECK on writes.
USINGgoverns which rows a user can read, update or delete;WITH CHECKgoverns what they can insert or change. An INSERT or UPDATE policy with noWITH CHECKcan limit reading to a user's own rows while still letting them write rows tagged with another workspace's or organization's ID.Views bypassing RLS. A standard Postgres view runs with its owner's privileges and does not apply the base table's RLS, so a convenience view such as a user summary can quietly expose the entire underlying table. On Postgres 15 and later — the version the post says Supabase runs — the fix is a single setting,
security_invoker = on.Loose grants. RLS decides rows; grants decide whether a role can touch the object at all. A broad grant to
anonorauthenticatedcompounds a broad policy, and the post specifically flagsanonSELECT privileges on tables containing emails, keys or financial records.Public storage buckets. Table-level RLS does nothing for Supabase Storage, which has its own access model. A bucket marked public serves any object to anyone who knows the URL; the suggested verification is to request a file with the anon key and see whether the request succeeds.
Realtime subscriptions. Realtime's
postgres_changesapplies RLS only when the configuration is correct and the table is in thesupabase_realtimepublication. Misconfigured, a client can receive change events for rows it should never see.SECURITY DEFINER functions. These run as their owner, often a role that RLS does not restrict, and are common in trigger helpers such as an
is_admin()check. If executable byanon, an attacker can call the function directly and gain owner-level access. Hardening advice from the post: setsearch_path = '', validateauth.uid()inside the function, and revoke execution fromanonunless it is genuinely needed.
The audit method
The post frames all seven cases as sharing one shape: RLS was enabled, so the work was assumed done. The author proposes a first pass of read-only catalog queries — against pg_policies, information_schema.views, role_table_grants, pg_publication_tables and pg_proc — that can be run in the Supabase SQL editor, and separately maintains a larger set of commented audit files covering roughly sixty checks plus a two-user isolation harness for verifying tenant isolation.
Why it matters
Supabase's anon key is public by design and ships in every client bundle, so the security boundary lives entirely in server-side configuration. The gap between "RLS enabled" and "actually private" is precisely where cross-tenant leaks sit, and the failure mode the post describes — a single-account test followed by a launch — is common in quickly assembled, AI-assisted apps. The practical test is not whether a flag is set, but what each low-privilege role can actually retrieve from every object it can reach.
- #supabase
- #postgresql
- #row-level-security
- #security
- #cloud