Founder & Lead Engineer, RAITHub
RAITHub ships and tests production software. See QA as a Service or talk to us.
Testing a Supabase app means proving three things an ordinary test suite often skips: that row-level security stops one user reading another's rows, that auth and sessions behave after deploy, and that your Edge Functions and storage are not open to the public key. RAITHub tests Supabase apps as a managed service, writing the tests into your own repository. Scope is quoted after a free 15-minute audit.
If you would rather have it tested for you, see how RAITHub would test this below, or go straight to QA as a service.
What is different about testing a Supabase app?
Supabase puts the database on the internet. It generates a REST and realtime API for every table in the public schema, and your app calls those endpoints from the browser with a public key. That design is safe only when the database itself decides who may see which rows, through row-level security (RLS). So the testing has to attack the data layer, not just the screens.
This is why a Supabase app can pass every click-through test and still leak data. The UI never shows another user's records, so a human tester never sees the bug; but anyone can call the table's endpoint directly with the public key and read everything. The test that matters is the one that makes that call on purpose. The same flaw, missing RLS policies, is the one behind the warning covered in "RLS disabled in public": what it means and how to fix it.
Which Supabase-specific risks should the tests cover?
Six areas carry most of the risk in a Supabase app. A test plan should name each one and say how it is checked.
| Area | What can go wrong | How it is tested |
|---|---|---|
| Row-level security | A table has RLS off, or a policy of using (true), so any key reads every row | Call the API as a second user and as an anonymous visitor; expect no rows |
| Auth and sessions | Sign-up, password reset, session refresh or sign-out break, often only after deploy | End-to-end auth journeys, plus redirect-URL and cookie checks |
| Edge Functions | A function skips its own auth check, or trusts input it should validate | Call each function with no token, a wrong-tenant token and bad input |
| Storage buckets | A public bucket exposes files by URL, or object policies are missing | Request another user's file by path; expect it to be refused |
| The service_role key | The secret key reaches the browser, bypassing every policy | Scan the client bundle and network calls for the key |
| Database functions and views | A security definer view or RPC runs as its owner and skips RLS | Call each RPC and view as a non-owner and check the rows returned |
The cross-tenant case is the one that bites hardest in a multi-user product. Users can see another tenant's data walks through how that leak happens and how to find it.
How do you prove row-level security actually works?
Test as the attacker would: as an anonymous visitor and as a second signed-in user. One trap first: the Supabase SQL editor usually runs as the table owner, who bypasses RLS, so a query that returns rows there proves nothing about what the browser sees. The honest test impersonates a real user. Supabase runs database tests with pgTAP from the supabase/tests folder and shows how to set a user role and JWT claim inside a transaction (Supabase testing documentation). A minimal test for a user-owned notes table:
Free checklist
AI-Built App Launch Readiness Checklist
25 checks before you let real users in. Enter your email and we’ll reveal it below (and send you a copy).
One email, the checklist, no spam. By submitting you agree we can email you this checklist and reply to your enquiry.
-- supabase/tests/notes_rls.test.sql
begin;
select plan(2);
-- An anonymous visitor sees nothing.
set local role anon;
select is_empty('select * from public.notes', 'anon sees no notes');
-- User A sees only their own rows.
set local role authenticated;
set local request.jwt.claim.sub = '11111111-1111-1111-1111-111111111111';
select results_eq(
'select count(*) from public.notes',
array[1::bigint],
'A sees one note'
);
select * from finish();
rollback;
Write the test before the policy is fixed and watch it fail; that is the proof the test can catch the bug. Run it on every change so the next AI-generated migration cannot quietly reopen a table.
Who should write the tests for a Supabase app?
It depends on who built it and whether anyone will keep the tests green.
| Route | Choose this when | Watch out for |
|---|---|---|
| Your own developers | They know the schema and have time to maintain tests | Under deadline, the RLS and Edge Function tests are the first dropped |
| A low-code test recorder | You want a few UI journeys covered fast | Recorders test screens, not the data API where the leaks are |
| A freelancer for one pass | You need a single pre-launch check | Nobody maintains the suite as the app changes |
| A managed QA service | You want RLS, auth and API tests written, gated in CI and kept current | Make sure the tests live in your repository and you own them |
What does a full Supabase test suite include?
A balanced suite tests the data layer hard and the screens lightly:
- Database tests that impersonate each role and prove RLS on every user-owned and tenant-scoped table, including inserts and updates, not just reads.
- API tests that call the generated endpoints with the public key as different users, and call each Edge Function with a missing, wrong and valid token.
- Auth journeys end to end: sign-up, email confirmation, password reset, session refresh and sign-out. Supabase auth often breaks only after a deploy, from redirect URLs, cookies or environment drift; Supabase auth fails after deploying to Vercel covers those causes.
- Storage tests that try to read another user's file by path and confirm it is refused.
- A key scan that fails the build if the service_role key appears in the client bundle.
How many of each follows the test pyramid: many fast database and unit tests, a solid API layer, and a handful of end-to-end journeys over the top. The method is in end-to-end test coverage for my SaaS.
How long does it take to test a Supabase app yourself?
For a small app you know well, expect two to five days to list every table and its intended owner, write RLS tests for the user-owned tables, add API tests for each Edge Function, and gate them in CI. The main risk of doing it yourself is testing only the happy path in the SQL editor, where the owner role hides the leak, and never calling the API as a second user, which is exactly where the expensive bug lives.
When do you need outside help, and when not?
You need help when the app holds real user data, when an AI tool generated the migrations and you are not sure which tables have policies, or when the service_role key may be in places you cannot trace. You do not need outside help when it is one or two tables with obvious owners and no sensitive data: apply the policies, run the tests above, and rerun Supabase's Security Advisor. A note on limits: this is functional and application-level security testing against OWASP guidance. It is not a CREST- or PCI-certified penetration test, and it produces no compliance attestation.
Why RAITHub for testing a Supabase app?
Because RAITHub treats a missing policy as a failed build, not a finding. On Sundor Skin, a B2B wholesale platform RAITHub built, every buyer query runs under PostgreSQL row-level security across a 146-table core; CI replays all 76 migrations on an empty database and fails if a buyer-scoped table lacks a policy, and an IDOR suite among its 530+ tests tries to read other buyers' data. Sundor Skin runs on PostgreSQL directly, not Supabase, but RLS is the same PostgreSQL feature and the discipline carries over. There is no published case study of testing a Supabase or AI-built app, so judge the service on the free audit, not on a claimed result.
How RAITHub would test this
- Scope: list every table and its intended owner, every Edge Function and storage bucket, and the auth journeys that matter; confirm where the service_role key is used.
- Attack the data layer: RLS tests per role on every user-owned and tenant-scoped table, API tests that call endpoints and functions as a second user, and a scan for the secret key in client code.
- Gate it: the suite runs in your CI on every change, so a failing policy or auth journey blocks the release.
- Handover: tests and CI configuration in your repository, a short guide, full IP assigned to you and a standard NDA.
Buy it as a monthly QA plan, a fixed-price one-off audit, or a dedicated QA team that RAITHub manages and bills monthly. Testers are never placed under your management. Start with a free 15-minute technical audit, then a written fixed quote; no rates are published. Tell RAITHub about your Supabase app.
Frequently asked questions
What does it mean to test a Supabase app?
It means proving the data layer is safe, not just that the screens work: that row-level security stops cross-user reads, that auth and sessions behave after deploy, and that Edge Functions, storage and the service_role key are not exposed. The tests call the generated API as different users and gate in CI.
Why can my Supabase app leak data even when the UI looks fine?
Because Supabase exposes every public table through an API that anyone can call with the public key. If a table has row-level security off, the UI never shows other users' rows, but a direct API call returns them all. The leak is invisible until a test calls the API as a second user.
Do you test Supabase Edge Functions?
Yes. Each function is called with no token, a wrong-tenant token and valid input, to check it enforces its own auth and validates what it receives, rather than trusting the caller.
Can you add tests to a Supabase app you did not build?
Yes. RAITHub lists every table and its intended owner, writes tests that pin down current behaviour, and gates them in CI before widening coverage, so the tests catch regressions without changing how the app works.
Is this a penetration test?
No. This is functional and application-level security testing against OWASP guidance, written into your repository. It is not a CREST- or PCI-certified penetration test and produces no compliance attestation. If you need one, use a certified pentest vendor.
Will the tests live in my repository?
Yes. The tests and CI configuration are committed to your repository and the IP is assigned to you, so the suite keeps protecting you after the engagement ends.
Related posts
Ready to discuss your project?
Book a free 15-minute technical audit with our engineering team.