Vibe coding security
What is Row Level Security (RLS)?
Row Level Security is the rule layer that decides which rows of a table each user can see and change — and it's the single most important setting protecting data in Supabase and other Postgres-based apps.
Short answer
Row Level Security (RLS) is a database feature that controls which individual rows a user is allowed to read or change. Instead of trusting your app code to filter data, the database enforces policies — rules like "a user can only see rows where user_id matches their account" — on every query. RLS is why a public database key can ship safely in a frontend: the key only grants what your policies allow.
Key takeaways
- Row Level Security (RLS) is a database feature that controls which individual rows each user is allowed to read or change — enforced by the database itself, not just your app.
- With RLS, you write policies: rules like 'a user can only see rows where the user_id matches their own account.' The database applies them to every query.
- RLS is why a public database key (like the Supabase anon key) is safe to ship in a frontend: the key only grants what your policies allow.
- The dangerous mistake is a table with RLS turned off, or a policy open to everyone — then anyone holding the public key can read or write the whole table.
- RLS protects data at the row level; it's the backbone of multi-user app security in Postgres-based stacks like Supabase.
A plain-English definition
Imagine a single orders table holding every customer's orders. Without row-level rules, any query that can reach the table can read all of it. Row Level Security changes that: it lets you attach policies to the table so the database decides, row by row, whether the current user is allowed to see or modify each one.
A typical policy reads almost like a sentence: allow a user to select a row only when that row's user_id equals the logged-in user's id. Once that policy exists, every read of the table automatically returns just the caller's own rows — even if the app code forgets to filter. The protection lives in the database, which is exactly where you want it.
How RLS policies work
RLS is enabled per table, and then you write one or more policies covering different actions. The key idea is that each action can have its own rule:
Enable RLS on the table
Until you do, the table is either fully open or fully closed depending on the key used — enabling RLS is the switch that makes policies apply.
Read (SELECT) policy
Decides which rows a user can see — e.g. only their own.
Insert / Update / Delete policies
Decide which rows a user can create or change. These must be scoped just as tightly as reads — a common gap is locking down reads but leaving writes open.
In Supabase, policies are written in SQL and usually reference the authenticated user's id (via auth.uid()). The step-by-step version, including the exact mistakes to look for, is in the Supabase RLS checklist.
Why RLS makes a public key safe
Apps built with tools like Lovable, Bolt, or v0 ship a public database key — the Supabase anon key — in the browser. That alarms people, but it's by design: the key is just an identifier that says "an anonymous or logged-in user is calling." What that caller can actually do is decided entirely by your RLS policies.
So the security question is never "is my anon key visible?" (it's supposed to be) — it's "are my RLS policies correct?" The key that must stay secret is the service_role key, which bypasses RLS entirely and should never appear in frontend code or a browser-exposed environment variable. See which environment variables get exposed to the browser for where that goes wrong.
The most common RLS mistake
The gap that ships most often in AI-built apps is simple and invisible from the user interface: a table with RLS turned off, or a policy open to everyone. When that happens, anyone holding the public key can read or write the whole table directly, skipping your app's screens entirely.
Why a scan can't fully confirm RLS for you
An external scan can detect an exposed service_role key and obvious open endpoints, but it can't log into your database or read your policies — so it can't prove your RLS rules are correct. That part is yours to verify: enable RLS on every table with real data, scope each policy to the right user, and test that one logged-in user can't reach another's rows.
Frequently asked questions
- What is Row Level Security in simple terms?
- Row Level Security (RLS) is a database feature that decides, for each row in a table, whether a given user is allowed to read or change it. Instead of trusting your application code to filter data, the database itself enforces rules — called policies — on every query. For example, a policy can say 'only return rows where the user_id column equals the logged-in user's id,' so each user automatically sees only their own data.
- Why is the Supabase anon key safe if it's public?
- Because Row Level Security, not secrecy, is what protects your data. The Supabase anon key is meant to ship in the browser; it only lets the holder do what your RLS policies allow. With RLS on and correct policies, someone holding the anon key still can't read another user's rows. The key that must stay secret is the service_role key, which bypasses RLS entirely and should never appear in a frontend.
- What happens if RLS is turned off?
- If a table has RLS disabled — or has a policy that's open to everyone — then anyone with your public database key can read, and possibly write, the entire table directly, bypassing your app's screens. This is the most common and most serious gap in AI-built apps, because the app can look and work perfectly while the underlying table is wide open. Every table with real or private data should have RLS enabled and scoped policies.
- Is Row Level Security the same as authentication?
- No. Authentication confirms who a user is (login); Row Level Security controls what data that user can access once identified (authorization, at the row level). They work together: RLS policies typically reference the authenticated user's id to decide which rows they may see. You need both — logging users in doesn't protect your data unless policies also restrict what each user can reach.
Check your app for exposed keys
GuardMint scans your live app for exposed secrets — including a service_role key that would bypass RLS — and obvious public exposure. No signup required for your first score.