Mockly

glossary

Forced RLS (FORCE ROW LEVEL SECURITY)

Forcing RLS guarantees ownership filters cannot be bypassed by privileged sessions. 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 “Forced RLS (FORCE ROW LEVEL SECURITY)” means (plain English)

Run ALTER TABLE ... FORCE ROW LEVEL SECURITY so Postgres always evaluates policies, even for owners.

How Forced RLS (FORCE ROW LEVEL SECURITY) works in Supabase/Postgres (technical)

When RLS is forced, every session (including table owners and superusers) runs the USING/WITH CHECK expressions, preventing set-role overrides from skipping the guard.

Attack paths & failure modes for Forced RLS (FORCE ROW LEVEL SECURITY)

  • Owner script bypasses non-forced RLS: A cleanup job runs as the owner and touches multiple tenants when cleaning stale data.
  • Migration creates a table without forced RLS: A migration adds a metadata table for invitations or logs and skips the security review.
  • Owner script bypasses non-forced RLS: RLS was not forced, so the script skipped the policies and mutated rows across tenants.
  • Migration creates a table without forced RLS: Client credentials could reach the table because its RLS settings were incomplete when it landed in production.
  • RLS is enabled but not forced, so owner-like operational paths behave differently than normal client traffic.
  • A backfill or cleanup job accidentally touches rows outside the intended tenant boundary because the security behavior drifted.
  • New tables ship with inconsistent RLS settings because migrations don’t enforce a baseline.
  • Teams verify security only through the UI, not via direct API access tests and repeatable checks.

Why Forced RLS (FORCE ROW LEVEL SECURITY) matters for Supabase security

Without forcing, owner-like scripts or migrations can read or write rows without ever hitting a policy, creating inconsistent security models.

Common Forced RLS (FORCE ROW LEVEL SECURITY) mistakes that lead to leaks

  • Enabling RLS but leaving it unforced for convenience.
  • Running scripts with owner privileges and forgetting to force afterwards.
  • Assuming ENABLE ROW LEVEL SECURITY alone blocks all bypasses.
  • Owner script bypasses non-forced RLS: RLS was not forced, so the script skipped the policies and mutated rows across tenants.
  • Migration creates a table without forced RLS: Client credentials could reach the table because its RLS settings were incomplete when it landed in production.

Where to look for Forced RLS (FORCE ROW LEVEL SECURITY) in Supabase

  • Table settings: confirm RLS is enabled and (for sensitive tables) forced.
  • Operational code paths (jobs, scripts, migrations) and what database role they effectively run as.
  • Drift between environments: dev/staging/prod may not share the same RLS/grant posture.

How to detect Forced RLS (FORCE ROW LEVEL SECURITY) 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.
  • Owner script bypasses non-forced RLS: Operational scripts are part of your threat model.
  • Owner script bypasses non-forced RLS: Force RLS to prevent silent bypasses.
  • Owner script bypasses non-forced RLS: Backfills should be tenant-scoped and auditable.
  • Re-test after every migration that touches security-critical tables or functions.

How to fix Forced RLS (FORCE ROW LEVEL SECURITY) (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:
  • Owner script bypasses non-forced RLS: Force RLS on sensitive tables, route operational jobs through tenant-scoped backend logic, and add regression checks for RLS being forced.
  • Migration creates a table without forced RLS: Treat every new table as security-critical: enable and force RLS, revoke anon/authenticated grants, and expose operations through backend endpoints.

Verification checklist for Forced RLS (FORCE ROW LEVEL SECURITY)

  1. Pick one sensitive table and verify its RLS enabled + forced settings match your intended policy.
  2. Attempt direct REST queries using client credentials and confirm access is denied for sensitive tables.
  3. Run an operational job in a safe environment and confirm it only touches the intended tenant/user scope.
  4. After your next migration, re-run the same checks to ensure defaults didn’t regress.
  5. Owner script bypasses non-forced RLS: Operational scripts are part of your threat model.
  6. Owner script bypasses non-forced RLS: Force RLS to prevent silent bypasses.
  7. Owner script bypasses non-forced RLS: Backfills should be tenant-scoped and auditable.
  8. Owner script bypasses non-forced RLS: Verify RLS is forced after every migration.

SQL sanity checks for Forced RLS (FORCE ROW LEVEL SECURITY) (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 Forced RLS (FORCE ROW LEVEL SECURITY) drift (so it doesn’t come back)

  • Add a migration checklist: new tables are locked down first (RLS enabled/forced where appropriate, client grants revoked).
  • Prefer backend-only endpoints so operational access is explicit and testable.
  • Schedule periodic audits for RLS settings drift across environments.
  • Keep one reusable verification test for “Owner script bypasses non-forced RLS” and rerun it after every migration that touches this surface.
  • Keep one reusable verification test for “Migration creates a table without forced RLS” and rerun it after every migration that touches this surface.

Rollout plan for Forced RLS (FORCE ROW LEVEL SECURITY) 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 Forced RLS (FORCE ROW LEVEL SECURITY) (real scenarios)

Owner script bypasses non-forced RLS

Scenario: A cleanup job runs as the owner and touches multiple tenants when cleaning stale data.

What failed: RLS was not forced, so the script skipped the policies and mutated rows across tenants.

What fixed it: Force RLS on sensitive tables, route operational jobs through tenant-scoped backend logic, and add regression checks for RLS being forced.

Why the fix worked: Forcing enforcement makes both client and operational access obey the same filters.

Key takeaways:

  • Operational scripts are part of your threat model.
  • Force RLS to prevent silent bypasses.
  • Backfills should be tenant-scoped and auditable.
  • Verify RLS is forced after every migration.

Read full example: Owner script bypasses non-forced RLS

Migration creates a table without forced RLS

Scenario: A migration adds a metadata table for invitations or logs and skips the security review.

What failed: Client credentials could reach the table because its RLS settings were incomplete when it landed in production.

What fixed it: Treat every new table as security-critical: enable and force RLS, revoke anon/authenticated grants, and expose operations through backend endpoints.

Why the fix worked: Locking down defaults prevents new tables from accidentally creating exposures.

Key takeaways:

  • New tables should be locked down on day one.
  • Enable and force RLS as a repeatable checklist item.
  • Revoke client grants before shipping features.
  • Backend endpoints make access easier to verify.

Read full example: Migration creates a table without forced RLS

Real-world examples of Forced RLS (FORCE ROW LEVEL SECURITY) (and why they work)

Related terms

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

FAQ

Is Forced RLS (FORCE ROW LEVEL SECURITY) 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 Forced RLS (FORCE ROW LEVEL SECURITY)?

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

Lock down a public table (backend-only access)

/templates/access-control/lock-down-public-table

cross

Next.js backend-only Supabase access

/integrations/nextjs-backend-only-supabase

cross

Pricing

/pricing