How I Would Fix database rules leaking customer data in a Supabase and Edge Functions mobile app Using Launch Ready.
The symptom is usually ugly and obvious: a user logs into the mobile app and sees another customer's rows, profile fields, orders, or private messages. In...
How I Would Fix database rules leaking customer data in a Supabase and Edge Functions mobile app Using Launch Ready
The symptom is usually ugly and obvious: a user logs into the mobile app and sees another customer's rows, profile fields, orders, or private messages. In the worst case, an Edge Function is returning data it should never have access to, or a Supabase table policy is too broad and lets anyone read too much.
The most likely root cause is a broken Row Level Security setup, usually combined with one of these mistakes: a missing policy, an overly permissive `select` policy, service role usage in the wrong place, or an Edge Function that bypasses auth checks. The first thing I would inspect is the exact path from mobile client to Supabase table: auth session, policy logic, function code, and whether the request is using anon key or service role key.
If this is leaking real customer data, I would treat it as a production incident first and a code issue second. That means I would stop the bleed before I try to "clean up" the architecture.
Triage in the First Hour
1. Freeze risky deploys.
- Pause new mobile releases.
- Pause Edge Function deploys if they are still changing.
- If needed, disable the specific endpoint or feature flag that exposes customer data.
2. Confirm the blast radius.
- Check which tables are exposed.
- Check whether leakage affects all users or only certain roles.
- Check whether unauthenticated users can see data, or only authenticated users can see other accounts' records.
3. Inspect Supabase Auth and RLS state.
- Open the affected tables in Supabase.
- Verify that Row Level Security is enabled on every customer-facing table.
- Review existing policies for `select`, `insert`, `update`, and `delete`.
4. Inspect Edge Functions logs.
- Look for service role usage.
- Look for missing JWT validation.
- Look for responses returning raw database rows without filtering by `user_id` or tenant id.
5. Review recent commits and deployments.
- Find the last change to policies, schema, or function code.
- Check if someone added a "temporary" admin bypass that never got removed.
- Check if a migration changed ownership columns or nullable constraints.
6. Check client-side requests from the mobile app.
- Verify which key is used by the app.
- Confirm the app does not embed any privileged secret.
- Inspect network calls for direct table access versus function calls.
7. Audit monitoring and error spikes.
- Look at failed auth attempts, unusual query volume, and support complaints.
- Check whether there was a sudden jump in records returned per request.
8. Preserve evidence before changing too much.
- Export current policies.
- Snapshot relevant function code.
- Keep a copy of logs tied to timestamps so you can explain what happened later.
-- Quick RLS sanity check select schemaname, tablename, rowsecurity from pg_tables where schemaname = 'public' order by tablename;
Root Causes
| Likely cause | What it looks like | How I confirm it | |---|---|---| | RLS disabled | Any authenticated user can query too much data | Check table settings in Supabase and inspect `rowsecurity = false` | | Over-broad policy | Policy uses `true`, `auth.uid() is not null`, or weak filters | Read policy SQL and test with two different user sessions | | Service role used in app path | Mobile client gets admin-level access indirectly | Search code for service role key usage outside trusted server code | | Edge Function skips auth checks | Function returns data without validating JWT claims | Review function entrypoint and request headers | | Wrong ownership column | Policy filters on `user_id`, but rows use `account_id` or tenant id | Compare schema to policy conditions and sample records | | Join leaks through views/functions | A view or RPC exposes related rows without RLS protection | Test direct table access versus view/RPC access separately |
The most common failure I see is not "Supabase is insecure." It is that the team assumed RLS would save them after shipping an admin shortcut, a broad select policy, or a function that bypasses normal authorization.
The Fix Plan
1. Stop public exposure immediately.
- Disable any endpoint returning sensitive rows until you confirm authorization logic.
- If necessary, ship a temporary maintenance state in the app for affected screens only.
2. Turn on RLS everywhere customer data lives.
- Enable RLS on all tables containing personal data, billing info, messages, projects, files, or internal notes.
- Do not leave exceptions because "this one table is internal." Internal tables become external leaks through joins fast enough.
3. Replace broad policies with explicit ownership rules.
- Write policies that bind each row to an authenticated user or tenant id.
- Use least privilege for each action separately: read, create, update, delete.
4. Remove service role access from client paths.
- The mobile app should never contain a service role secret.
- Any admin-only operation should move into a trusted backend path with strict auth checks.
5. Harden Edge Functions as if they were public APIs.
- Validate JWTs on every request that touches customer data.
- Reject requests with missing auth context instead of guessing intent from headers alone.
- Return only fields needed by the screen.
6. Fix schema mismatches before rewriting everything else.
- Make sure every protected row has a reliable owner reference such as `user_id` or `tenant_id`.
- Backfill missing ownership values before enforcing strict policies if needed.
7. Add guardrails around dangerous queries.
- Limit response shape with explicit selects instead of broad wildcard fetches where possible.
- Avoid RPCs that return mixed-tenant results unless they are truly admin-only and isolated.
8. Rotate secrets if there was any chance of exposure.
- Rotate keys used by functions or deployment tooling if logs show they were printed or stored badly.
- Update environment variables in all environments after rotation.
9. Redeploy in small steps.
- Ship policy changes first in staging with production-like test accounts.
- Then deploy function fixes behind monitoring so you can catch regressions quickly.
10. Document what changed and why.
- Write down which tables were exposed, which policies were fixed, and which functions now enforce auth explicitly.
- This matters because teams forget incident details fast and repeat them later under deadline pressure.
My rule here is simple: do not patch this by adding more frontend checks alone. If database rules are wrong at the source, any client-side guard will fail the moment someone calls your API directly.
Regression Tests Before Redeploy
Before I let this back into production, I want tests that prove one user cannot see another user's data under normal use and under awkward edge cases.
Acceptance criteria:
- User A can only read rows owned by User A or their tenant account.
- User B cannot read User A's rows through direct table queries, views, RPCs, or Edge Functions.
- Unauthenticated requests receive no sensitive data and clear errors where appropriate.
- Admin-only actions require explicit privileged access and do not work from the mobile client key.
QA checks:
- Test with two real user accounts seeded with separate records.
- Test direct reads against every affected table after login refreshes and expired sessions.
- Test behavior on slow networks so stale cached responses do not show old private data incorrectly displayed as current state to another user session on shared devices when applicable to your app flow; also verify logout clears local sensitive caches where needed for mobile UX safety
- Test both staging and production-like config values because many leaks happen only when env vars differ between environments.
Risk-based test plan: 1. Direct table query as valid user A returns only A's rows. 2. Direct table query as valid user B returns zero of A's rows every time across 20 repeated requests. 3. Edge Function call as user A returns only permitted fields for A's account context. 4. Missing auth header returns 401 or 403 consistently without partial data leakage. 5. Service-role-only routes reject client-originated traffic completely.
I would also run one negative test set focused on API security:
- Tampered JWT
- Expired session token
- Missing tenant claim
- Cross-account record ID guessed from sequential IDs
- Cached response after logout on shared device
If this touches customer records at scale, I would want at least 90 percent coverage on authorization tests around the affected tables and functions before redeploying.
Prevention
The leak should be treated as an authorization failure pattern, not just one broken query. To stop it coming back:
- Make RLS part of code review gating for every schema change touching customer data.
- Require explicit ownership tests for each new table before merge.
- Ban service role keys from any mobile bundle or public-facing runtime environment entirely unless there is a documented exception with review signoff from someone senior enough to say no safely because hidden admin paths are one of the fastest ways to create support load later when customers notice cross-account visibility bugs
- Add logging on denied access attempts so you can spot probing patterns without logging sensitive payloads themselves
- Monitor unusual row counts per request so one endpoint cannot quietly start returning 10x more records than expected
- Set alerts on function errors after auth changes because teams often fix security but break onboarding at the same time
- Keep response payloads small so accidental overfetching does less damage
- Use least privilege environment variables per deployment target
- Review third-party packages that touch auth helpers or API clients because dependency drift can break assumptions silently
From a UX angle, do not hide security failures behind vague messages forever. Give users a clear re-auth prompt when sessions expire so they do not retry broken flows endlessly and flood support with "my app shows someone else's account" reports caused by stale sessions on shared devices.
From a performance angle, keep authorization checks cheap enough that developers do not get tempted to bypass them later. In practice that means indexed ownership columns like `user_id` or `tenant_id`, predictable query plans around those filters, and avoiding expensive joins inside hot paths where p95 latency should stay under 200 ms for normal reads.
When to Use Launch Ready
Use Launch Ready when you need me to stop this kind of issue fast without turning your product into a science project.
For this specific incident class, Launch Ready fits best after I have fixed the authorization bug but before you re-open traffic broadly. It gives you a controlled launch path: DNS checked once, SSL verified once, redirects confirmed once? No special chars: redirects confirmed once? Let's keep ASCII only: redirects confirmed once; monitoring live; secrets reviewed; deployment safe; handover documented.
What you should prepare before booking:
- Supabase project access with admin rights
- Edge Functions repo access
- Mobile app repo access
- List of affected screens and APIs
- Current production domain settings if release touches web assets too
- Any recent incident notes or screenshots from users
If you already know customers saw private records in production then I would not guess my way through it over Slack threads alone. Book Launch Ready at https://cal.com/cyprian-aarons/discovery so I can scope whether this needs just hardening plus redeploying in 48 hours or whether there is deeper schema cleanup required first at https://cyprianaarons.xyz
References
1. roadmap.sh API Security Best Practices: https://roadmap.sh/api-security-best-practices 2. roadmap.sh Cyber Security: https://roadmap.sh/cyber-security 3. Supabase Row Level Security docs: https://supabase.com/docs/guides/database/postgres/row-level-security 4. Supabase Edge Functions docs: https://supabase.com/docs/guides/functions 5. OWASP API Security Top 10: https://owasp.org/API-Security/
---
Take the next step
If this is a problem in your product right now, here is what to do next:
- [Use the free Cyprian tools](/tools) - estimate cost, score app risk, check launch readiness, or pick the right service sprint.
- [Book a discovery call](/contact) - I will tell you honestly whether you need a sprint or if you can DIY the next step.
*Written by Cyprian Tinashe Aarons - senior full-stack and AI engineer helping founders rescue, launch, automate, and scale AI-built products.*
Cyprian Tinashe Aarons — Senior Full Stack & AI Engineer
Cyprian helps founders rescue, secure, deploy, and automate AI-built apps with production-grade engineering, launch systems, and AI integration.