Skip to content
Hidden Logic Lab

Review Postgres RLS policies before merge

Cursor or Claude Code applies a SKILL.md on migration PRs with RLS: FORCE RLS, USING vs WITH CHECK gaps, role bypass, and tenant isolation holes — with merge-blocking findings.

·Data·45 min·Deep

An rls-review SKILL.md for schema PRs, a policy matrix comment per table, and no tenant row leakage merged as a quick policy tweak.

  1. 1

    Run negative tests as app role

    Use local Postgres with non-superuser role matching production. The skill should require cross-tenant attempts to fail.

  2. 2

    Keep a tenant table inventory

    Link the inventory from the skill. New tables without RLS should stand out against the list.

  3. 3

    Pair with sql-migration-review skill

    Schema PRs should load both skills: DDL lock risk from migration review, isolation from this one.

The prompt to paste

Write SKILL.md for reviewing Postgres Row Level Security (RLS) policies before merge. Focus on tenant isolation and privilege bypass — not general SQL style.

Repository context:
[migration folder and how RLS is applied: Supabase, raw SQL, drizzle, etc.]
[tenant key column pattern e.g. organization_id, tenant_id]
[roles: which DB roles app uses — names only: authenticated, service_role, app_user]
[whether FORCE ROW LEVEL SECURITY is team standard]

PR context (paste when using):
[PR link, migration files, tables gaining or changing policies]

SKILL.md must include:
1. Purpose — RLS is the last line when app code passes wrong tenant id; a hole here is cross-customer data exposure.
2. When to use — any PR adding/changing ENABLE ROW LEVEL SECURITY, policies, or tenant columns; when not to use — comments-only migrations with no policy change.
3. Hard rules for findings:
   - Block: RLS enabled but not FORCE and table owner/bypass role can read all rows; policy only USING without WITH CHECK on INSERT/UPDATE allowing cross-tenant writes; missing policy on a role the app uses (implicit deny vs accidental allow); service_role used in user-facing paths without separate guard; policies referencing mutable user-controlled columns without validation; tenant column nullable without NULL isolation rule.
   - Warn: SELECT policy permissive but UPDATE restrictive mismatch; multiple permissive policies OR-ed in surprising ways; policies using current_setting without safe search_path; new table without RLS while siblings have it; SECURITY DEFINER functions accessing table without fixed search_path.
   - Note: policy naming — never block on naming alone.
4. Review steps the agent must execute:
   - List each affected table: RLS on/off, FORCE on/off, owner role.
   - For each policy: command (SELECT/INSERT/UPDATE/DELETE/ALL), roles targeted, USING expression, WITH CHECK expression — paste simplified form in PR comment.
   - Verify tenant predicate appears in both USING and WITH CHECK where applicable; flag USING-only on writable tables.
   - Check bypass paths: superuser, table owner, BYPASSRLS attribute, service key in app server — map to [team policy placeholder].
   - Cross-check app queries: ORM filters vs policy — write [confirm app sets session variable] if using set_config pattern.
   - Suggest verify queries: SET ROLE ...; attempt cross-tenant SELECT/INSERT as negative test — commands only, no prod credentials.
5. PR comment template: policy matrix (table — op — role — tenant guard — gaps), findings, merge recommendation.
6. Safe patterns section: FORCE RLS on all tenant tables; single tenant_id predicate function; separate service_role migrations with human approval gate.
7. Two example prompts: add organization_id to table vs loosen SELECT policy for analytics.
8. Pitfalls: testing only as superuser, copying Supabase template without WITH CHECK, assuming API layer replaces DB RLS, breaking migrations that disable RLS briefly without follow-up migration in same PR.
9. Done means: every Block resolved, policy matrix complete for touched tables, rollback or down migration noted if policy change is risky, no real tenant ids in comment.

Rules:
- Do not invent table or column names outside migration paste or repo paths given.
- Do not suggest disabling RLS in production to fix a bug.
- Output only SKILL.md markdown.

USING without WITH CHECK on INSERT lets users write rows they cannot read — but other tenants may read them. The skill must Block writable policies missing WITH CHECK tenant guards.

Sample case written by Hidden Logic Lab. Not affiliated with Cursor or Claude Code.

More in Data

Get new cases in your inbox

Weekly notes on new use cases and agent workflows. No product pitches.

Occasional notes on new cases. No product pitches in the first note.