Mockly

glossary

Over-permissive RLS Policies

Over-permissive RLS policies let too many rows through or cover too many actions, defeating the point of row security. This page explains it in plain English, then goes deeper into how it works in Supabase/Postgres, what commonly goes wrong, and how to fix it without relying on fragile client-side rules.

What “Over-permissive RLS Policies” means (plain English)

A policy becomes over-permissive when its conditions always pass or don’t tie rows to the requester, so the table behaves as if it were public.

How Over-permissive RLS Policies works in Supabase/Postgres (technical)

If the policy expression relies on claims that are always true or ignores tenant context, Postgres treats every row as allowed and the policy no longer enforces least privilege.

Attack paths & failure modes for Over-permissive RLS Policies

  • Policy condition always true: A developer pasted a snippet under deadline and forgot to make it tenant-aware.
  • Policy missing tenant boundary: A B2B app stored all customers in one table with a tenant_id column but only checked auth.uid().
  • Policy condition always true: The condition was a tautology, so Postgres allowed every row.
  • Policy missing tenant boundary: The policy failed to enforce tenant_id = auth.token().tenant_id, so a user could read any tenant’s rows.
  • Policies use conditions that are always true (effectively public access).
  • Policies check authentication but not authorization (logged-in ≠ allowed).
  • Multi-tenant apps forget tenant boundaries, allowing cross-tenant reads/writes.
  • Policies rely on client-supplied values rather than server-trusted claims.
  • Policies become outdated after schema changes and silently widen access.

Why Over-permissive RLS Policies matters for Supabase security

Attackers can enumerate or mutate rows because the policy logic never filters them, which makes a protected table behave like a public endpoint.

Common Over-permissive RLS Policies mistakes that lead to leaks

  • Copying policy snippets that include tautologies or fail to bind rows to auth.uid().
  • Treating broad authenticated conditions as safe without tenant or ownership checks.
  • Assuming policies protect data while leaving grants untouched.
  • Policy condition always true: The condition was a tautology, so Postgres allowed every row.
  • Policy missing tenant boundary: The policy failed to enforce tenant_id = auth.token().tenant_id, so a user could read any tenant’s rows.

Where to look for Over-permissive RLS Policies in Supabase

  • Policy expressions: do they bind rows to auth.uid() or a tenant membership table?
  • Tables with multiple policies that interact in unexpected ways.
  • Any use of broad roles (authenticated) without row ownership constraints.
  • Places where the UI “filters” rows but the database policy does not.

How to detect Over-permissive RLS Policies issues (signals + checks)

Use this as a quick checklist to validate your current state:

  • Try the same queries your frontend can run (anon/authenticated). If sensitive rows come back, you have exposure.
  • Verify RLS is enabled and (for sensitive tables) forced.
  • List policies and look for conditions that don’t bind rows to a user or tenant.
  • Audit grants to anon / authenticated on sensitive tables and functions.
  • Policy condition always true: Treat copied policy snippets as untrusted until evaluated.
  • Policy condition always true: Authorization must bind rows to a user or tenant.
  • Policy condition always true: Favor deny-by-default when unsure.
  • Re-test after every migration that touches security-critical tables or functions.

How to fix Over-permissive RLS Policies (backend-only + zero-policy posture)

Mockly’s safest default is backend-only access: the browser should not query tables, call RPC, or access Storage directly.

  1. Decide which operations must remain client-side (often: none for sensitive resources).
  2. Create server endpoints (API routes or server actions) for required reads/writes.
  3. Apply hardening SQL: enable+force RLS where relevant, remove broad policies, and revoke grants from client roles.
  4. Generate signed URLs for private Storage downloads on the server only.
  5. Re-run a scan and confirm the issue disappears.
  6. Add a regression check to your release process so drift doesn’t reintroduce exposure. Fixes that worked in linked incidents:
  • Policy condition always true: Drop the policy, adopt backend-only access, and implement authorization for that flow inside a server endpoint.
  • Policy missing tenant boundary: Require tenant claims in the policy or move the access behind a backend endpoint that validates membership.

Verification checklist for Over-permissive RLS Policies

  1. Test access as a user who should not see a row (different user/tenant) and confirm it’s denied.
  2. Simplify policies: prefer explicit membership joins over complex boolean logic.
  3. Remove policies you don’t fully understand; default to deny-by-default where possible.
  4. Revoke broad grants and keep privileged access on the server only.
  5. Add regression tests for cross-tenant access and re-run after migrations.
  6. Policy condition always true: Treat copied policy snippets as untrusted until evaluated.
  7. Policy condition always true: Authorization must bind rows to a user or tenant.
  8. Policy condition always true: Favor deny-by-default when unsure.

SQL sanity checks for Over-permissive RLS Policies (optional, but high signal)

If you prefer evidence over intuition, run a small set of SQL checks after each fix.

The goal is not to memorize catalog tables — it’s to make sure the access boundary you intended is the one Postgres actually enforces:

  • Confirm RLS is enabled (and forced where appropriate) for tables tied to this term.
  • List policies and read them as plain language: who can do what, under what condition?
  • Audit grants for anon/authenticated and PUBLIC on the tables, views, and functions involved.
  • If Storage is involved: review bucket privacy and policies for listing/reads.
  • If RPC is involved: review EXECUTE grants for functions and whether privileged functions are server-only.

Pair these checks with a direct API access test using client credentials. When both agree, you can ship the fix with confidence.

Over time, keep a small “query pack” for the checks you trust and run it after every migration. That’s how you prevent quiet regressions.

Prevent Over-permissive RLS Policies drift (so it doesn’t come back)

  • Keep RLS policies small and composable; avoid “magic” conditions that are hard to reason about.
  • Add a review checklist for any policy change: who can read, who can write, and how do we prove it?
  • Prefer backend-only access for sensitive workflows to reduce policy surface area.
  • Keep one reusable verification test for “Policy condition always true” and rerun it after every migration that touches this surface.
  • Keep one reusable verification test for “Policy missing tenant boundary” and rerun it after every migration that touches this surface.

Rollout plan for Over-permissive RLS Policies fixes (without breaking production)

Many hardening changes fail because teams revoke direct access first and only later discover missing backend paths.

Use this sequence to reduce both risk and outage pressure:

  1. Implement and verify the backend endpoint or server action before permission changes.
  2. Switch clients to that backend path behind a feature flag when possible.
  3. Then revoke direct client access (broad grants, permissive policies, public bucket reads, or broad EXECUTE).
  4. Run direct-access denial tests and confirm authorized backend flows still succeed.
  5. Re-scan after deployment and again after the next migration.

This turns security fixes into repeatable rollout mechanics instead of one-off emergency changes.

Incident breakdowns for Over-permissive RLS Policies (real scenarios)

Policy condition always true

Scenario: A developer pasted a snippet under deadline and forgot to make it tenant-aware.

What failed: The condition was a tautology, so Postgres allowed every row.

What fixed it: Drop the policy, adopt backend-only access, and implement authorization for that flow inside a server endpoint.

Why the fix worked: Backend code can explicitly check ownership, log decisions, and avoid the subtle mistakes that happen in policy expressions.

Key takeaways:

  • Treat copied policy snippets as untrusted until evaluated.
  • Authorization must bind rows to a user or tenant.
  • Favor deny-by-default when unsure.
  • Use backend endpoints to centralize complex rules.

Read full example: Policy condition always true

Policy missing tenant boundary

Scenario: A B2B app stored all customers in one table with a tenant_id column but only checked auth.uid().

What failed: The policy failed to enforce tenant_id = auth.token().tenant_id, so a user could read any tenant’s rows.

What fixed it: Require tenant claims in the policy or move the access behind a backend endpoint that validates membership.

Why the fix worked: Backend code makes tenant boundaries explicit and easier to test than subtle policy expressions.

Key takeaways:

  • Tenant checks must appear everywhere.
  • Login alone is not enough for multi-tenant data.
  • Backend-only access reduces accidental cross-tenant reads.
  • Add regression checks whenever tenant logic changes.

Read full example: Policy missing tenant boundary

Real-world examples of Over-permissive RLS Policies (and why they work)

Related terms

  • Row Level Security (RLS) → /glossary/row-level-security
  • Public Table Exposure → /glossary/public-table-exposure

FAQ

Is Over-permissive RLS Policies enough to secure my Supabase app?

It’s necessary, but not sufficient. You also need correct grants, secure Storage/RPC settings, and a backend-only access model for sensitive operations.

What’s the quickest way to reduce risk with Over-permissive RLS Policies?

Remove direct client access to sensitive resources, enable/force RLS where appropriate, and verify via a repeatable checklist that anon/authenticated cannot query what they shouldn’t.

How do I verify the fix is real (not just a UI change)?

Attempt direct API queries using the same client credentials your app ships. If the database denies access (401/403) and your backend endpoints still work, your fix is effective.

Next step

Want a quick exposure report for your own project? Run a scan in Mockly to find public tables, storage buckets, and RPC functions — then apply fixes with verification steps.

Explore related pages

parent

Glossary

/glossary

sibling

Public Table Exposure

/glossary/public-table-exposure

sibling

Row Level Security (RLS)

/glossary/row-level-security

cross

Remove over-permissive RLS policies (adopt deny-by-default)

/templates/access-control/remove-over-permissive-policies

cross

Policy condition always true

/examples/over-permissive-rls-policies/policy-condition-always-true

cross

Pricing

/pricing