Most “review my RLS” prompts return generic advice about enabling RLS. Real RLS bugs are subtler: a missing WITH CHECK on UPDATE, a service_role bypass that leaks via a function, an auth.uid() comparison against the wrong column. These 15 prompts each hunt one specific RLS failure mode.
Who this is for
Solo founders shipping Supabase apps, code reviewers auditing schema PRs, security engineers preparing pen-test fixtures, indie devs before public launch.
When not to use these prompts
Skip these for non-Supabase Postgres (the auth.uid() and storage parts don’t apply). Skip for prototypes where RLS is intentionally disabled.
Prompt anatomy / structure formula
Every RLS review prompt should carry six elements:
- Role: who the AI plays (architect / SRE / QA lead / release captain).
- Context: repo / framework / runtime versions / files or diff in scope.
- Goal: one concrete deliverable — review notes, plan, checklist, test file, handoff doc.
- Constraints: what AI MUST NOT do (don’t rewrite, don’t auto-format, don’t guess versions).
- Output format: numbered findings, markdown table, JSON, unified diff, or runnable code.
- Examples / signal: 1-2 examples of “good” output, or what bad output looks like.
Best for
- Pre-launch RLS audit
- Schema migration PR review
- Post-incident policy hardening
- Role / service_role usage audit
- Storage bucket access review
15 copy-ready prompt templates
1. RLS enabled coverage check
Run first — a missing RLS toggle invalidates every other policy.
You are a Supabase security reviewer. Audit the schema dump below. For every table in `public`: (1) Is RLS enabled? (2) Does it have at least one policy? (3) Is FORCE ROW LEVEL SECURITY enabled (relevant for table owner access)? Output a table: schema.table | RLS enabled | policy count | force RLS | risk.
2. auth.uid() comparison correctness
Review every policy USING / WITH CHECK clause that references `auth.uid()`. For each: (1) Is it compared against the column that actually holds the owner (e.g., user_id, owner_id, profile_id)? (2) Could the comparison silently pass for nulls? (3) Are joined tables also constrained, or is the JOIN unprotected? List findings: policy name | issue | fix sketch.
3. INSERT / UPDATE / DELETE policy coverage
For each table with RLS enabled, list which commands have policies: SELECT, INSERT, UPDATE, DELETE. Flag tables missing policies for any command — the default is deny, but partial policies often leave gaps (e.g., UPDATE without WITH CHECK lets users move rows to other owners). Output as a matrix.
4. WITH CHECK vs USING audit
Review every UPDATE and INSERT policy. For each: (1) Does it have BOTH USING (for the pre-image) and WITH CHECK (for the post-image)? (2) On UPDATE, does WITH CHECK prevent reassigning ownership to another user? (3) On INSERT, does WITH CHECK prevent inserting on behalf of another user? List violations with file:line.
5. service_role bypass audit
Find every usage of `service_role` key in the codebase (server functions, edge functions, migrations, background jobs). For each: (1) Is it strictly necessary, or could `authenticated` work? (2) Is the call wrapped in a function that re-checks ownership? (3) Are any service_role queries reachable from user input without validation? File:line + severity.
6. Storage bucket RLS review
Audit Supabase Storage bucket policies. For each bucket: (1) public vs private flag, (2) SELECT/INSERT/UPDATE/DELETE policies on `storage.objects` filtered to this bucket, (3) Does the path convention encode ownership (e.g., `{user_id}/...`) and does the policy enforce that prefix? (4) Are signed URLs used where private buckets need temp access? Output: bucket | policies | risks.
7. SECURITY DEFINER function audit
List every Postgres function marked SECURITY DEFINER (including those Supabase generates). For each: (1) Does it set `search_path` explicitly to prevent search_path attacks? (2) Does it re-validate auth.uid() if it bypasses RLS? (3) Is the function callable by anon? (4) Is grant correct? Findings with severity.
8. Realtime subscription policy audit
Audit Supabase Realtime publication and policies: (1) Which tables are in `supabase_realtime` publication? (2) Do those tables have SELECT policies that constrain rows visible to a user? (Realtime respects RLS — if SELECT is loose, broadcasts leak.) (3) Are any sensitive columns published when they shouldn't be? List risks.
9. Multi-tenant isolation review
This app is multi-tenant via `tenant_id` (or {tenantColumn}). Audit: (1) Every tenant-scoped table has tenant_id and an RLS policy comparing it to the user's tenant claim, (2) JWT claim extraction (`auth.jwt() ->> 'tenant_id'` or similar) is consistent, (3) Cross-tenant reads via JOINs are blocked. Output: table | tenant policy | leak risk.
Variables to swap: tenantColumn — e.g., tenant_id, org_id, workspace_id
10. Role-based policy audit
If this schema uses role columns (admin, member, viewer), audit role policies: (1) Are roles read from a profile table or a JWT claim? Pick one — mixing is a footgun, (2) Do admin-only mutations check role inside the policy, or only in the app? (3) Is there a way to escalate role via UPDATE on profiles? List findings.
11. Migration policy diff review
Use on a migration PR.
Below is a migration diff. For RLS changes: (1) Any policy dropped without replacement? (2) Any new table missing RLS? (3) Any column type change that breaks an existing policy comparison? (4) Are policies idempotent (CREATE OR REPLACE / DROP IF EXISTS)? Output PR-ready review comments.
Variables to swap: migration diff text
12. Anon vs authenticated split review
For every policy, classify which role it applies to (anon, authenticated, service_role). Flag: (1) policies that apply to anon when they should be authenticated only, (2) policies that apply to authenticated when only specific roles should access, (3) policies missing role filter (default = all roles).
13. Policy performance review
Review RLS policies for performance: (1) Policies that call functions per-row instead of inlining the auth check, (2) Policies that JOIN large tables — these become per-row subqueries, (3) Missing index on the column compared to auth.uid(), (4) `auth.uid()` invoked multiple times per policy instead of cached. Suggest specific indexes and rewrites.
14. RLS gap → red-team scenario
Take the policies below and write 5 red-team scenarios: each is a JWT + a SQL query + the expected (denied) result. Then say which of the 5 would actually succeed against current policies and why. Use this as a regression-test seed.
15. RLS findings → fix plan
Run last — converts findings into a migration sequence.
Take all RLS findings above and group them into ordered migrations. For each migration: (1) Title, (2) SQL diff (DROP / CREATE / ALTER), (3) Pre-deploy check (count rows that would now be denied — sanity check), (4) Rollback. Mark which migrations need maintenance windows.
Common mistakes
- Reviewing only USING and forgetting WITH CHECK — UPDATE / INSERT leaks fly under the radar.
- Trusting service_role for “internal” code without re-checking ownership — every service_role path is a privileged surface.
- Comparing auth.uid() against the wrong column (id instead of user_id) — silent zero-row results hide the bug.
- Enabling RLS but forgetting FORCE ROW LEVEL SECURITY — table owner queries bypass policies.
- Letting realtime publication broadcast tables with loose SELECT policies — leak surface multiplies.
- Mixing role lookup sources (profile table vs JWT) — race conditions on role change.
- Skipping migration review on RLS PRs — dropped policies appear as added security but are actually regressions.
How to push results further
- Always start with template 1 (enabled coverage) — every other audit is meaningless if RLS is off.
- Require the reviewer to write a red-team scenario (template 14) — it surfaces what static review misses.
- For multi-tenant apps, get auth.jwt() claim extraction consistent across every policy.
- Cache auth.uid() in a
WITHclause or function once per query for perf. - Pair the RLS review with a service_role audit — most leaks happen there, not in policy text.
- Re-run the audit after each migration PR — RLS state drifts silently.
- Snapshot policies to a
/supabase/policies.snapshot.sqlfile in repo; review diffs over time.
FAQ
- How do I test policies without running them?: Static review covers structure. For runtime: use Supabase’s
select set_config('request.jwt.claims', ...)trick in psql to simulate users. - Can AI generate the policies for me?: Yes for first drafts, but always run this review on AI-generated policies. They commonly miss WITH CHECK and role filters.
- Do I need RLS if my app does auth checks?: Yes. App-only checks fail open the moment someone reaches the DB directly (PostgREST, SQL editor, leaked anon key). RLS is the last line.
- Why does my SELECT return zero rows after enabling RLS?: Default deny. You need at least one SELECT policy that matches the user’s rows. Run template 3 to find missing policies.
- Should service_role calls always re-check ownership?: If the call accepts user-supplied identifiers, yes. Re-check or wrap in a SECURITY INVOKER function.
- How do I prevent role escalation via UPDATE on profiles?: Add a column-level check or a trigger that rejects role changes from authenticated users. Cover this in template 10.