fixes / launch-ready

How I Would Fix database rules leaking customer data in a React Native and Expo subscription dashboard Using Launch Ready.

The symptom is usually simple and scary: a user logs in, opens the subscription dashboard, and sees data that belongs to another customer. In business...

How I Would Fix database rules leaking customer data in a React Native and Expo subscription dashboard Using Launch Ready

The symptom is usually simple and scary: a user logs in, opens the subscription dashboard, and sees data that belongs to another customer. In business terms, that is a trust break, a support fire, and potentially a privacy incident.

The most likely root cause is weak authorization at the database layer, not just a bad UI check. If I were inspecting this first, I would look at the rules or policies that control reads on the customer tables, then trace which client queries are pulling too much data through the Expo app.

Triage in the First Hour

1. Check the affected screens in the Expo app.

  • Open the subscription dashboard as two different test users.
  • Confirm whether the leak happens on first load, after refresh, or only after navigation.

2. Inspect recent deploys.

  • Review the last production build from Expo EAS.
  • Check whether a new screen, query, or env var change shipped with the leak.

3. Look at database access logs.

  • Find read events for customer tables around the time of the reports.
  • Check whether one user ID is requesting rows tied to another account.

4. Review database rules or policies.

  • Inspect row-level security rules, Firestore rules, Supabase policies, or custom API auth checks.
  • Verify whether reads are scoped by tenant ID or authenticated user ID.

5. Audit the client queries.

  • Search for broad selects like "get all subscriptions", "fetch all customers", or missing filters.
  • Check whether any admin-style endpoint is being called from the mobile app.

6. Verify auth session handling.

  • Confirm token storage and refresh logic in Expo SecureStore or equivalent.
  • Look for stale sessions that might be attaching the wrong identity to requests.

7. Check dashboards and monitoring.

  • Review error tracking, API logs, and uptime alerts for spikes in 200 responses with abnormal payload sizes.
  • Large successful responses often hide data leaks better than obvious failures.

8. Freeze risky changes.

  • Pause releases until access control is confirmed.
  • If customer data exposure is active, disable public reads before anything else.
## Quick sanity check for common policy files and broad queries
grep -R "select.*\*" .
grep -R "allow read" .
grep -R "policy" .

Root Causes

| Likely cause | What it looks like | How I confirm it | |---|---|---| | Missing row-level security | Any authenticated user can read all rows | Test with two accounts and compare returned row IDs | | Overbroad policy condition | Policy checks auth but not tenant ownership | Read policy logic and verify tenant_id or owner_id match | | Client-side filtering only | App fetches all records then hides some in UI | Inspect network calls and raw response payloads | | Shared admin endpoint used by mobile app | Dashboard hits an endpoint meant for internal staff | Review route names, headers, and permission checks | | Broken session mapping | User A gets User B's token or profile context | Trace login flow and token refresh behavior | | Bad seed/test data promoted to prod | Demo accounts or fixtures are exposed in live tables | Compare production table contents with migration history |

1. Missing row-level security

This is the most dangerous version because it fails at the database boundary. If RLS or equivalent rules are off, any bug above it becomes a data leak.

I confirm this by querying as two separate users and checking whether each can read only their own rows. If both can see the same subscription records, I treat it as an authorization failure, not a frontend bug.

2. Overbroad policy condition

Sometimes there is a rule in place, but it checks only that someone is signed in. That protects against anonymous access but not cross-customer access.

I look for conditions like "authenticated = true" without tenant scoping. For a subscription dashboard, every read should usually match `user_id`, `account_id`, or `tenant_id`.

3. Client-side filtering only

This pattern shows up when developers fetch everything into React Native state and then filter locally by plan name or account label. That may look fine in testing with one account but leaks data as soon as multiple customers exist.

I confirm this by inspecting network traffic and seeing whether the app receives more rows than it should display. If yes, the fix must move filtering to the server or database layer.

4. Shared admin endpoint used by mobile app

A lot of AI-built products accidentally reuse an internal endpoint because it was faster to ship. The problem is that internal routes often trust staff roles or skip strict checks.

I confirm this by reviewing route names, auth middleware, and any service role keys used from the client side. A mobile app should never rely on a privileged backend key embedded in Expo code.

5. Broken session mapping

If tokens are cached incorrectly or refreshed against the wrong account context, one user's session can inherit another user's permissions. This is less common than bad rules but still serious.

I confirm by clearing app storage on device emulators and re-testing login/logout flows repeatedly across two accounts. If identity changes are inconsistent across sessions, I inspect SecureStore usage and auth state hydration.

The Fix Plan

My approach would be boring on purpose: lock down access first, then clean up queries, then redeploy with tests that prove isolation works.

1. Stop further leakage immediately.

  • Disable public reads on sensitive tables if needed.
  • Temporarily restrict dashboard access to verified staff or beta users while fixing policies.

2. Move authorization into the database layer.

  • Require every customer-facing read to be scoped by authenticated user ID or tenant ID.
  • Deny by default if ownership cannot be proven.

3. Remove client-side trust assumptions.

  • Do not let React Native decide who can see what after fetching raw records.
  • Only send each user the minimum data needed for their view.

4. Replace broad queries with scoped queries.

  • Add explicit filters for `user_id`, `account_id`, `org_id`, or equivalent fields.
  • If using Supabase/Postgres RLS, write policies that enforce those relationships directly.

5. Rotate exposed secrets if any privileged key was shipped.

  • Check Expo config files and build-time env vars immediately.
  • If a service role key ever reached a client bundle, rotate it now.

6. Validate every write path too.

  • Leaks often happen alongside weak update/delete permissions.
  • Confirm users can only modify their own billing details and subscription settings.

7. Add safe logging before redeploying.

  • Log denied access attempts without storing sensitive payloads.
  • Keep enough detail to debug without exposing customer PII in logs.

8. Ship behind a controlled rollout if possible.

  • Use staged deployment or feature flags so you can stop if something breaks login flow or billing visibility.

For databases like Postgres with RLS through Supabase-style policies, I would usually want something conceptually like this:

-- Example pattern only: ensure each user sees only their own rows
create policy "read_own_subscription"
on subscriptions
for select
using (user_id = auth.uid());

That pattern is simple on purpose. The exact syntax depends on your stack, but the principle does not change: prove ownership at read time inside the database.

Regression Tests Before Redeploy

Before I ship this fix back into production, I want proof that data separation holds under normal use and edge cases.

  • Two-account isolation test
  • Account A cannot see Account B's subscriptions anywhere in list views, detail views, search results, notifications, or cached screens.
  • Fresh install test
  • On a clean device simulator with no stored session, unauthenticated users cannot load protected dashboard data.
  • Session switch test
  • Log out of Account A, log into Account B on the same device, then confirm no stale cache shows A's records.
  • Role boundary test
  • Regular customers cannot hit admin-only endpoints even if they manually trigger requests from dev tools or intercepted traffic inside QA environments.
  • Offline cache test
  • If using local persistence in Expo/React Native Query/Apollo/Redux Persist, verify cached data does not bleed between accounts after logout/login.
  • Error-state test
  • When authorization fails, show a clear empty state instead of partial data from another account.
  • Acceptance criteria: no cross-account record appears in any screen capture after logout/login cycles across 10 repeated runs.
  • Performance sanity check
  • Dashboard first load should still stay under p95 2 seconds for metadata views after adding stricter filters or joins where practical.
  • If performance regresses beyond that threshold, add indexing rather than loosening security again.

Prevention

If I were hardening this product after launch, I would put guardrails at four layers: code review, security controls, QA coverage, and monitoring.

Code review guardrails

  • Every customer-facing query must show its authorization filter in code review.
  • No direct use of privileged service keys inside Expo client code.
  • Any new table needs an ownership model before it ships live access paths.
  • I would reject PRs that say "we filter it in UI later" for sensitive data.

Security guardrails

  • Default deny on all sensitive tables and endpoints.
  • Rotate secrets stored in build pipelines every time access scope changes.
  • Lock down CORS where web surfaces exist alongside mobile APIs.
  • Rate limit auth-sensitive endpoints to reduce abuse and noisy retries during incidents.

QA guardrails

  • Add automated tests for multi-user isolation on every release candidate.
  • Include regression cases for logout/login switching on shared devices because mobile users do this constantly.
  • Run one manual exploratory pass using two real accounts before production deploys affecting billing data or entitlements.

Monitoring guardrails

  • Alert on unusual response sizes from dashboard APIs because leaks often inflate payloads quietly.
  • Track denied authorization attempts separately from generic errors so you can see attack noise versus broken logic.
  • Review logs weekly for repeated cross-account access attempts from specific sessions or IP ranges if your privacy policy allows it safely.

UX guardrails

A secure fix still has to feel clean inside React Native and Expo. When access is denied or data is unavailable due to permission scope changes, show an empty state that explains what happened without exposing other customers' records.

That reduces support tickets because users understand they are signed into the wrong account type instead of thinking their billing history vanished randomly.

When to Use Launch Ready

Use Launch Ready when you need me to stop guessing and get this under control fast: domain setup, email routing issues tied to notifications, Cloudflare protection, SSL cleanup, deployment hardening, secret handling,

For this kind of incident-prone subscription dashboard, Launch Ready fits when:

  • your app works but production safety is weak,
  • your deployment process feels fragile,
  • you need DNS redirects,

subdomains, Cloudflare, SSL, caching, DDoS protection, SPF/DKIM/DMARC, environment variables, and uptime monitoring sorted without dragging out a full rebuild,

  • you want a handover checklist so your team knows what changed and what to watch next week,

What I need from you before starting:

  • Expo project access plus repository access,
  • database admin or scoped security-policy access,
  • current production domain registrar details,
  • Cloudflare account access if already connected,
  • environment variable list,
  • screenshots of affected screens,
  • one working customer account plus one test account,
  • any recent support tickets showing how users noticed the leak,

My goal in that sprint would be simple: make sure launch infrastructure does not keep compounding an application-level security mistake while we fix it properly upstream.

Delivery Map

References

  • https://roadmap.sh/api-security-best-practices
  • https://roadmap.sh/code-review-best-practices
  • https://roadmap.sh/qa
  • https://supabase.com/docs/guides/database/postgres/row-level-security
  • https://docs.expo.dev/develop/development-builds/introduction/

---

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.*

Next steps
About the author

Cyprian Tinashe AaronsSenior Full Stack & AI Engineer

Cyprian helps founders rescue, secure, deploy, and automate AI-built apps with production-grade engineering, launch systems, and AI integration.