For Supabase apps · RLS

RLS bugs don’t throw.
They just leak.

A wrong policy returns the wrong rows. Unit tests pass. Types check. Then user A sees user B’s invoices in the UI. Lastest signs in as A, screenshots, signs in as B, and asserts B can’t see A’s data. Real end-to-end RLS coverage.

Two-user RLS flowVisual + DOM assertions$0 self-hosted
Lastest test list showing user A and user B RLS coverage flows
A vs B.

RLS bugs hide between layers. That’s why they ship.

Two reasons RLS coverage falls through every test layer until it shows up in a support ticket - or a security disclosure.

01

Bad RLS doesn’t throw. It returns the wrong rows.

A wrong USING clause, a missed auth.uid() filter, an OR where you needed an AND - Postgres returns extra rows happily. The query succeeds. The HTTP status is 200. The UI renders them. The bug is invisible to anything except a real user navigating with the wrong identity.

  • No exception - every layer thinks it succeeded
  • Type checks pass - the shape is right, just not the rows
  • Unit tests miss it - they don’t hit the real RLS
  • The leak is a screenshot away from the user
02

Manual RLS QA breaks down at three users.

“Log in as user 1, then user 2, then user 3, on every PR” works for a sprint. After that, nobody does it. Asking an LLM to test RLS runs a different path every time, burns tokens, has no concept of cross-user isolation, and grades its own homework.

  • Non-deterministic - different rows poked each run
  • No screenshot record - “works on my machine”
  • Cookies bleed - sloppy QA misses the real bug
  • RLS regressions ship to prod every quarter

Two users. One green check.

Two features that turn RLS coverage from theoretical to deterministic.

01 · Multi-user record

Sign in as A. Then B. In isolated contexts.

Each Lastest test runs in a fresh browser context. Cookies, localStorage and storage state don’t bleed. Record one flow as user A: sign in, hit the data screen, screenshot. Record one flow as user B: sign in, hit the same screen, screenshot. The recorded test runs both - and asserts B doesn’t see A’s rows.

  • Deterministic Playwright code with self-healing selectors
  • DOM-text assertions: “invoice X must not appear”
  • Multi-step screenshots - sign-in, data screen, sign-out
Lastest recorder capturing user A sign-in flow for an RLS test

02 · Cross-user diff

Compare what A sees vs what B sees. Side by side.

Three diff engines (pixelmatch, SSIM, Butteraugli) plus DOM-text assertions tell you whether B suddenly started rendering A’s data - even if the columns are the same shape. AI failure triage separates real RLS regressions from layout flakes and env noise.

  • Auto-mask timestamps, UUIDs, relative times
  • Branch-to-branch screenshot compare for PR review
  • One-click slider to approve or reject each change
Visual diff slider comparing user A and user B dashboards

Supabase RLS testing FAQ

Why is RLS so hard to test?
RLS bugs don’t throw. A wrong policy doesn’t error; it just returns the wrong rows. Your unit tests pass, your TypeScript compiles, and the UI happily renders user B’s invoices to user A. The only reliable test is: sign in as A, capture what they see, sign in as B, capture what they see, then assert no overlap. That’s an end-to-end concern, not a unit test.
How does Lastest actually test RLS?
You record two flows. Flow A: sign in as user A, navigate to the data screen, capture screenshot + DOM snapshot. Flow B: sign in as user B (clean session), navigate to the same screen, capture. Lastest replays both deterministically on every push and surfaces the screenshots side-by-side. If user B suddenly sees user A’s rows, the visual diff catches it and the DOM assertion fails.
Can I assert specific rows are missing, not just visual?
Yes. The recorder captures DOM snapshots and visible text. You can assert "user A's invoice ID does not appear on user B's page" as a text-presence check, alongside the visual diff. Two layers (pixel and DOM) for one bug class that hides perfectly between them.
Does this work for Supabase + Next.js with server actions?
Yes. Lastest tests the running app. It doesn’t care whether queries run from a client component, a server component, a server action, or an edge function. If RLS leaks rows to the wrong user, the UI shows those rows, and the test catches it. Framework-agnostic by construction.
How do I manage two test users in CI?
Seed two users in your Supabase project (or use the test instance), pin their credentials in CI secrets, and let Lastest sign in fresh each run. Lastest uses isolated browser contexts per test, so cookies and localStorage don’t bleed between users.
What about RLS on stored procedures or RPC?
Same story: if the RPC returns leaked rows, the UI renders them. The visual plus DOM assertion catches it the same way. For backend-only RLS coverage you’d still want SQL-level tests, but Lastest catches the user-facing breach.
What does this cost?
Zero. Lastest is open source (FSL-1.1-ALv2), self-hosted via Docker. Two recorded RLS flows replay on every PR forever. No per-screenshot fee, no per-seat fee, no token cost on replay.

Test RLS for real.
Two users. One run.
In five minutes.

No cloud account. No API key. Point it at your Supabase-backed app, record A then B, and replay forever. Open source, FSL-1.1-ALv2 licensed, $0 forever.

★ Star & Install from GitHub →
install

# 1. clone

$ git clone https://github.com/las-team/lastest

 

# 2. start

$ cd lastest && docker-compose up -d

 

# 3. point at your Supabase app URL

$ open http://localhost:3000

 

View the source