Guides
What actually goes wrong with Supabase Row Level Security, why it goes wrong more often when an AI wrote the migration, and the SQL that fixes each case.
RLS disabled on a public table: the most common Supabase leak
2 rulesA table in the public schema with Row Level Security switched off returns every row to anyone holding your anon key. Here is the check, the fix, and how to keep it fixed.
The migration that added a table and forgot RLS
1 ruleGenerated migrations create tables in seconds and policies never. Find every public table shipped without Row Level Security, with the enable-and-policy SQL ready.
USING (true): the policy that protects nothing
1 ruleRLS enabled, dashboard satisfied, every row public. Constant-true policies are the quietest Supabase leak. Here is how to find and replace them.
WITH CHECK (true): anyone can write anything
1 ruleA write policy with a constant-true WITH CHECK turns your table into a public message board. Find open write paths and close them with scoped CHECK clauses.
INSERT policies without WITH CHECK let anyone write as anyone
1 ruleA missing WITH CHECK is not stricter than a loose one — Postgres falls back in surprising ways. Find INSERT policies that constrain nothing.
RLS enabled, zero policies: silently everything-denied
1 ruleThe safe-looking state that quietly breaks your app: RLS switched on, no policies behind it, every non-owner query returns empty. Detect and resolve it.
Policies scoped to service_role are decoration
1 ruleservice_role carries BYPASSRLS, so policies targeting it bind nobody. Why the dashboard generates them anyway, and what to write instead.
RESTRICTIVE policies: the modifier people accidentally grant with
1 ruleA RESTRICTIVE policy can only narrow access, never widen it. Misreading it leaves tables looking locked that are wide open — or the reverse.
Without FORCE RLS the table owner bypasses everything
manual checkRLS never binds the table owner unless you force it — and migrations run as the owner. The one-line fix and why generated policies omit it.
auth.uid() returning null in the wrong context
manual checkWhen auth.uid() evaluates to null — cron jobs, triggers, service calls — equality policies deny rather than error. Diagnose null-context failures.
Two permissive policies are OR’d together — and that surprises people
manual checkAdding a second permissive policy widens access instead of tightening it. How Postgres combines policies, the classic accidental-widening traps, and the audit query.
SECURITY DEFINER functions with an unset search_path
manual checkDefiner functions without a pinned search_path can be hijacked by schema objects in the caller-controlled path. The pin pattern and the audit query.
Direct GRANTs to anon that sidestep RLS thinking entirely
manual checkTable and routine GRANTs shape what PostgREST exposes independently of policies. Audit the grant matrix before it contradicts your policy design.
Views that execute with owner rights and skip policy checks
manual checkPre-15-style views run as their owner, so policies on underlying tables never apply. Find them, convert them, and know what RowShield covers.
The Supabase security gaps Lovable apps ship with
4 rulesLovable gets a working backend in an afternoon; policies are the step that comes after. The gaps these apps ship with, the requests that reveal them, the fixes.
World-readable tables in a Lovable build — and the one request that proves it
1 ruleA table in a Lovable build returns rows to anyone holding the anon key. Here is the single request that proves it, why dashboards still say protected, and the fix.
Bolt.new builds and the policies that were never written
1 ruleBolt scaffolds tables fast; CREATE POLICY lines rarely follow. Why enabled-but-policyless tables happen, the quiet deny-all they cause, and the policies to write.
Cursor-written migrations and the RLS line that never appeared
1 ruleCursor writes the CREATE TABLE and skips the ENABLE ROW LEVEL SECURITY line beneath it. How to review AI-generated migrations for the statements that decide exposure.
Claude Code + Supabase MCP: fast schema, slow policy
2 rulesThe Supabase MCP lets Claude Code apply schema changes in seconds while policy work lags behind. What drifts between sessions, the alerts that catch it, how to check.
Replit frontends and everything the anon key can reach
1 ruleA Replit frontend ships the Supabase anon key to every visitor. What that key reaches before policies exist, how to test it yourself, and how to bound it properly.
Taking a v0 + Supabase prototype to production safely
manual checkA v0 prototype with a real Supabase project behind it needs a short list of checks before real users arrive. The checklist, the SQL behind each item, the limits.
Translating Firebase rules thinking into RLS thinking
manual checkFirestore rules and Postgres RLS solve the same problem in different grammar. A translation guide for teams moving from Firebase to Supabase, with worked policies.
Edge functions hold service_role — verify the boundary actually holds
1 ruleEdge functions are the right home for service_role — usually. How the boundary fails in practice, how to verify yours holds, and the rotation order when it does not.
The server/client key split done wrong in Next.js + Supabase
1 ruleOne environment variable decides whether Next.js visitors hold an anon key or an RLS-bypassing service_role key. Where the split breaks, and how to prove yours right.
FlutterFlow + Supabase: the RLS half the tutorial skipped
manual checkFlutterFlow tutorials end at the API call; the policy half decides who reads what. The RLS work tutorials omit, with the catalog checks and policy SQL to do it properly.
Internal tools pointed straight at PostgREST
1 ruleRetool and Bubble connect straight to PostgREST, and the anon key decides what they see. Why internal tools leak, the request that tests it, and what finally bounds it.
Anyone can list my users table
1 ruleYour users or profiles table answers strangers with its contents. The one request that proves it, why dashboards still say protected, and the policy that closes it.
My API returns data with no login at all
1 ruleRows arrive from your REST endpoint with no session and no token beyond the public anon key. Why PostgREST answers, what each response shape means, and the fix.
The dashboard says protected, but the table is still readable
1 ruleRLS reads enabled, indicators look green, yet an unauthenticated request returns rows. The tautological policy behind it, and the queries that expose it.
Your storage bucket is public — so are all of its images
1 ruleObjects in a public bucket download from their URL with no auth check, and paths circulate. What public really serves, how to verify it, and the signed-URL fix.
An open storage.objects policy is a public bucket with extra steps
1 ruleThe bucket flag says private, yet objects download anyway. A permissive policy on storage.objects grants what the flag withheld — here is the query and the rewrite.
Queries got slower the day RLS went on
1 ruleRow Level Security evaluates its predicate against every candidate row, and an unindexed column turns each query into a sequential scan. The diagnosis, and one index.
The bill spiked and the only change was policies
2 rulesCompute costs climb after policies land when predicates force sequential scans and bare auth.uid() runs per row. Both habits, the SQL to find them, and the fixes.
The same Security Advisor warning keeps coming back
manual checkYou dismissed it, fixed it, watched it clear — and it returned. Why point-in-time checks meet regressing states, and how transition tracking ends the loop.
You fixed it Monday; it was back by Friday
manual checkFixed Monday, back Friday, nobody touched it. That is a regression — a process restoring yesterday. Where fixes get undone, how to prove it, and what labels it.
What a RowShield health score actually measures
manual checkWhat feeds the health score, what moves it, and what it deliberately ignores — described qualitatively, with a query to count the raw inputs yourself.
Schema drift in Supabase: what moves, what breaks, what watches
manual checkTables, policies, buckets and functions move after deploy. A field guide to Supabase schema drift, the failures it causes, and how continuous diffing catches it.
The new column your policy never mentioned
manual checkAdding a column can widen what existing policies expose or break their assumptions entirely. How schema additions interact with RLS, and how to catch the fallout.
Renames that quietly unhook a policy predicate
manual checkRename user_id to owner_id and every policy referencing user_id stops resolving - erroring loudly or comparing against nothing. Failure modes and the fix loop.
The migration that recreated the table without its policies
2 rulesDrop-and-recreate is the fastest way to change a table and the fastest way to lose every policy on it. Why it happens, the damage pattern, and the guard.
Restores that resurrect last month’s holes
manual checkPoint-in-time recovery rewinds data, schema and policies together. Last month’s fixed vulnerability comes back with your data. Plan for it.
Staging and production disagree about who can read
manual check"It works in staging" assumes the catalogs match. They rarely do - and the differences concentrate exactly where security decisions live.
Policy drift versus the advisor: point-in-time versus continuous
manual checkThe dashboard advisor answers "what is wrong right now". Drift detection answers "what changed since last time". Both matter; neither substitutes.
Anatomy of a RowShield drift alert
manual checkWhat each drift alert carries: the object that moved, the transition type, before-and-after context, severity mapping, and the remediation path when one exists.
Unindexed RLS predicates: the index your policy is begging for
1 ruleAn RLS policy filtering on an unindexed column turns every query into a sequential scan. Find the columns yourself, then fix each with one statement.
Wrapping auth.uid(): the InitPlan pattern, explained honestly
1 ruleA bare auth.uid() inside a policy is evaluated once per candidate row. Wrapped in a scalar subquery it runs once per statement, with identical semantics.
RLS patterns that survive millions of rows
manual checkRow Level Security adds work to every query, and the work compounds as tables grow. These are the policy patterns that stay fast past a million rows — reviewed by hand.
Per-row versus per-statement: how policy conditions really run
manual checkPolicy conditions are not run once per query. Here is the per-row evaluation model, what an InitPlan changes, and how to read a plan without inventing numbers.
Index strategy for org_id-style multi-tenant policies
manual checkNearly every Supabase schema filters each table by an org_id or user id. Getting the index shape right is the difference between a seek and a scan on every request.
Thinking like an RLS cost calculator
manual checkYou do not need a dashboard to reason about what an unindexed policy costs. A rough model of rows scanned and requests made gets you close, qualitatively and honestly.
A Supabase storage audit you can run in ten minutes
1 rulePublic buckets, open object policies and disabled RLS on storage.objects account for most storage incidents. Walk the checklist, then keep it checked continuously.
Signed URLs or a public bucket: deciding properly
1 ruleBoth models are legitimate; they answer different questions. Weigh exposure, expiry, revocation and caching per bucket, then keep whichever you choose monitored.
Exactly what an anon key can do before you write a policy
manual checkThe anon key is public by design and harmless while RLS bounds it. Here is what it reaches when policies are missing — table by table, surface by surface.
The two-key mental model, once and for all
manual checkOne Supabase key is meant to be public, the other must never leave the server. Anchor the distinction once — including where authenticated users fit — and stop guessing.
Rotating a service_role key that got out: rotate first
1 ruleA service_role key outside your server is an RLS bypass in someone else's hands. Rotate immediately, inventory what breaks, then move the callers server-side for good.
sb_publishable_ keys and the new API-key hierarchy
manual checkSupabase has been moving from JWT-shaped keys to explicit publishable and secret formats. What changes, what stays true, and how to migrate your environment variables.
Row Level Security alongside an external auth provider
manual checkRunning Auth0, Clerk or another provider in front of Supabase? Policies still bind everything. Two integration shapes exist, each with familiar, fixable traps.
Realtime authorisation and the policies people forget
manual checkPostgres change streams honour your RLS policies; broadcast and presence never touch the database. Know which half of Realtime each security rule governs.
Supabase security for solo founders: a one-person pass that holds
manual checkA practical security pass for the Supabase project behind your first paying customers: what to lock down this week, and what can wait until revenue lands.
Supabase security across client projects: an agency baseline
manual checkRun Supabase security across many client builds without a platform team: per-project baselines, tiered thresholds and monitoring that survives staff turnover.
From prototype to production: the Supabase checklist
manual checkThe pre-launch pass that separates a weekend prototype from a production Supabase backend: RLS forced on every table, keys scoped, buckets deliberate, backups proven.
Health data isolation on Supabase: configuration you can defend
manual checkIsolation requirements for health data on Supabase: relationship-scoped RLS on clinical tables, private document storage, and configuration watching over time.
Student data on Supabase: scoping enrolments, marks and messages
manual checkEnrolment tables, guardian access and pupil records: how education platforms on Supabase scope student data so one class structurally cannot read another.
Your internal tool is reachable by the whole internet
1 ruleInternal tools share the same public URL and anon key as your app. How world-readable ticket tables happen, and how RowShield’s anonymous probe catches them.
Multi-tenancy on Supabase: policy patterns that scale
manual checkOrganisation-scoped RLS done properly on Supabase: membership joins, owner overrides, shared-row exceptions, and the conventions that keep a growing schema legible.
Can tenant A read tenant B? A test you can run tonight
1 ruleA repeatable test for whether tenant A can read tenant B: forged JWT claims, SET ROLE, plus the anonymous probe behind RowShield rule ANON_TABLE_READABLE.
Forum databases on Supabase: public by design, private by policy
manual checkPublic posts are meant to be public; profiles, private messages and moderation notes are not. A posture pass for community forums running on Supabase.
Chat history is sensitive: securing an AI backend on Supabase
manual checkConversation history is the most sensitive table in an AI product. Scope it per user on Supabase, keep writes server-side, and fence the embeddings nobody thinks about.
Listings are public; everything around them is not
manual checkListings are public by design; drafts and seller details are not. Shape RLS for directory and marketplace tables on Supabase so publicity is a decision, not an accident.
An open contact table reads like a customer list, because it is one
manual checkA CRM contact table reachable through the anon key is a breach-shaped problem. Confirm the exposure, scope contacts to workspace members, and put watching in place.
Telemetry writes: RLS on IoT ingest tables
1 ruleDevice telemetry arrives from firmware you do not control. Constrain the write path with WITH CHECK so a spoofed device id cannot append into another fleet’s history.
Inheriting a Supabase project: the first-week audit
manual checkInheriting a Supabase project built by someone else? Map the reachable surface, reconstruct the access model and price the findings before changing anything.
What a SOC 2 auditor asks about your Supabase RLS posture
manual checkWhat SOC 2 examiners typically ask about database access, and what row-level security evidence looks like. Informational only — not legal or compliance advice.
Access-control posture for GDPR-minded teams on Supabase
manual checkWhat a defensible access-control posture looks like on a Supabase backend when personal data is in scope. Informational only — this page is not legal advice.
Demonstrating who changed your database policies, and when
manual checkPostgres keeps no native history of policy changes. How to reconstruct one with migrations and logs, and what RowShield records between scans automatically.
Security advisor vs continuous monitoring, drawn precisely
manual checkThe dashboard Security Advisor is a good point-in-time check. What continuous monitoring adds is the interval between looks: drift, regressions and behaviour.
Linting policy SQL versus probing the live project
manual checkStatic linters read policy definitions before they ship; live probes request the running API after. Different instruments, different failure classes, complementary.
pgTAP-style test harnesses versus managed RLS monitoring
manual checkTest harnesses assert policy behaviour inside CI against a database you point them at. Managed monitoring watches production continuously. They complement.
A fifteen-minute manual Supabase audit — and where it stops
manual checkAn ordered checklist for auditing a Supabase project by hand with psql and curl, an honest account of what a snapshot cannot tell you, and how to automate it.
Free ways to audit a Supabase project, honestly compared
manual checkFour free routes to a Supabase security audit — the built-in advisor, hand-run SQL, self-made probes and RowShield's free tier — and what each yields.
A free Supabase RLS audit: paste a URL, read the findings
manual checkPaste your project URL and get a free RLS audit of your live surface plus catalog checks. GET requests only, nothing stored, no account required.
Probe your live app: what its own public key gives away
2 rulesYour deployed app hands every visitor an anon key, sometimes something stronger. Probe your live URLs from outside and read exactly what they expose.
Is my Supabase secure? An honest answer, and how to find yours
manual checkSecurity reduces to three verifiable things: RLS on every reachable table, policies granting only what you intend, bypass keys off the client.
Checking whether RLS is enabled: one query, read honestly
manual checkOne query against pg_class lists every public-schema table with its RLS flag. Here is the query, the FORCE flag people miss, and the fix for a false.
Listing every RLS policy: pg_policies, column by column
manual checkpg_policies renders every policy in your project with its command, roles and conditions. Here is the query, each column explained, and what to hunt for.
Testing RLS as the anon caller, not as the admin
1 ruleAdmin sessions bypass the machinery under test. Ask PostgREST as the anon key instead: here is the request, and how to read rows, empty arrays, errors.
Tautological policies: when RLS is switched on and says nothing
1 ruleA tautological policy such as USING (true) admits every row to every role it names while RLS reports enabled. Definition, detection query, honest fix.
Will enabling RLS break my app? What actually changes
manual checkTurning RLS on denies every client row until a policy grants it. Ship enable and policies in one migration and the break window is a single deploy.
The Supabase security checklist, complete and current
manual checkEvery check that matters for a Supabase backend across tables, keys, storage, performance and process, each with the query that verifies it.
RLS glossary: every term, in plain language
manual checkRLS, policies, USING and WITH CHECK, tautology, FORCE, BYPASSRLS, service_role, anon, PostgREST, drift, transitions and remediation SQL defined plainly.
Why the same finding returns — and what finally breaks the cycle
manual checkFixed on Friday, flagged again Monday: findings return because the fix lived in the database while the cause lives in the process. Break both halves.
RowShield for agencies: every client backend, one pane
manual checkFifteen client projects on one dashboard, alerts routed per destination with thresholds you choose, evidence you can hand each client. Built for rosters.
Adding a policy check to your Supabase CLI workflow today
manual checkWire rowshield scan into the same flow as supabase db push: one command, three exit codes, remediation SQL printed on failure. Shipped interface, no roadmap.
A GitHub Actions RLS check: the design, and what works today
manual checkWhat a planned GitHub Actions integration would check and fail on, plus the pattern you can adopt today with plain steps and scheduled scans. No dates promised.
Auditing preview deployments: the plan, and today's practice
manual checkPreview deploys get their own environment variables and their own surprises. What a planned preview-audit integration would do, and how to check previews today.
Slack alerts for RLS changes: contents, thresholds, cadence
manual checkWhat a RowShield Slack alert contains, how per-destination severity thresholds work, and why alerts fire on transitions only. Slack delivery starts on Indie.
Discord delivery for findings: embeds, thresholds, dedupe
manual checkHow RowShield renders findings as Discord embeds, how per-destination thresholds route them, and why repeats stay quiet. Discord delivery starts on the Team plan.
Driving scans from your own tooling: today and planned
manual checkScript scans today through the CLI and its exit codes; a planned API would expose the same semantics over HTTP. Design goals stated, dates promised none.
Policy-as-code habits for Supabase: Terraform and beyond
manual checkWhere RLS policies should live, which Terraform habits prevent drift, and how scheduled scans catch the divergence that code review alone cannot.
The migration review habit that stops most Supabase drift
manual checkMost RLS regressions enter through ordinary migrations. A pairing rule, a short reviewer checklist, and scheduled scans as the safety net underneath.
Prevent Supabase RLS bypass
4 rulesFind tables with Row Level Security disabled, policies that evaluate to true, and writes with no WITH CHECK — then keep them from coming back.
Detect a service_role key in your client bundle
1 ruleThe service_role key bypasses RLS entirely. Find it in your deployed JavaScript before someone else does — free, no signup, no code access needed.
Why your Supabase bill spikes after adding RLS
2 rulesUnindexed RLS predicates and bare auth.uid() calls turn every query into a sequential scan. Find both, with the SQL to fix them.
Check whether your Lovable app is leaking data
3 rulesFree, no-signup check for tables readable with your public anon key, RLS switched off, and service_role keys in the bundle.
Bolt.new shipped tables without RLS
3 rulesFind every table Bolt created without Row Level Security, and get the enable-plus-policy SQL generated from your real columns.
Audit the Supabase policies Cursor wrote
3 rulesCursor writes plausible RLS. Check for policies that evaluate to true, writes without WITH CHECK, and predicates with no index.
Detect Supabase schema drift after migrations
4 rulesKnow when a table appears without policies, a policy is rewritten, RLS is switched off, or a bucket flips to public — and when a fix regresses.
SECURITY DEFINER views and functions that bypass RLS
manual checkA SECURITY DEFINER view runs as its owner and ignores the caller RLS policies. Here is how to find them yourself, with the catalog queries.
What the Supabase Security Advisor does not do
7 rulesThe advisor is a point-in-time check you have to open. Continuous scanning, drift diffing, alerts and CI exit codes are what it is missing.
Continuous RLS monitoring for Supabase
7 rulesScheduled catalog audits, drift diffing, Slack and webhook alerts on transitions, and CI exit codes — on a paid plan above the free tier.