Supabase emailed me at 9 PM. All my tables were publicly accessible.
I shipped a production app with Row Level Security disabled. Anyone with my project URL could read, edit, and delete everything. I didn't notice. Supabase did.
The email arrived Tuesday at 5:26 PM.
"Critical issue: Table publicly accessible. Anyone with your project URL can read, edit, and delete all data in this table because Row Level Security is not enabled."
Project: ai-morning-digest. The digest I'd just made public. The one with a subscribers table.
I'd been running this pipeline for a week. Every morning at 8 AM it fetched 150 items, scored them with Claude Haiku, stored everything in Supabase, and sent emails. Working perfectly. Publicly exposed the entire time.
What RLS actually is
Supabase is built on Postgres. By default, any table you create is accessible to anyone who knows your project URL and anon key -- both of which appear in your client-side code. Row Level Security is a Postgres feature that lets you define exactly who can do what to each row.
Without it: your anon key is basically a master key.
With it: you define policies. Service role gets full access. Anon key gets read-only on specific tables. Subscribers table gets nothing from the outside.
It's not on by default. You have to enable it. I forgot.
The fix
Six SQL statements. Five minutes.
Enable RLS on each table, create a service role policy for the pipeline, create public read policies for the dashboard. Done. Supabase security advisor went from one critical error to zero errors, zero warnings, zero suggestions.
ALTER TABLE digests ENABLE ROW LEVEL SECURITY;ALTER TABLE digest_items ENABLE ROW LEVEL SECURITY;ALTER TABLE subscribers ENABLE ROW LEVEL SECURITY;
The pipeline uses a service role key that bypasses RLS entirely -- so nothing broke. The public dashboard uses the anon key and now only has read access to digests and digest_items. The subscribers table is locked down completely.
The part that bothers me
This is the second time I've shipped something with a security issue I didn't catch myself.
Field Note #006: Claude Code committed my Contentful Management Token to git history. GitGuardian caught it.
Field Note #016: I left RLS disabled on a public production database. Supabase caught it.
Both times, an automated system found what I missed. Both times, the fix was fast. Both times, I'm glad someone was watching.
I'm building in public and moving fast. That combination creates real exposure. The automated watchers -- GitGuardian, Supabase security advisor, GitHub secret scanning -- are not optional paranoia. They're load-bearing.
What I'm changing: RLS is now the first thing I set up after creating a Supabase table. I added it to the migration file so the next person who forks the repo gets it by default.
One more thing
The email arrived five days after I made the repo public. Five days where anyone could have found the project URL, hit the API, and exfiltrated the subscribers list.
Nobody did. Probably because nobody was looking. But probably isn't a security policy.