fixes / launch-ready

How I Would Fix database rules leaking customer data in a React Native and Expo automation-heavy service business Using Launch Ready.

The symptom is usually blunt: one customer logs in and sees another customer's records, automations, or contact details. In a React Native and Expo app,...

How I Would Fix database rules leaking customer data in a React Native and Expo automation-heavy service business Using Launch Ready

The symptom is usually blunt: one customer logs in and sees another customer's records, automations, or contact details. In a React Native and Expo app, that often means the app is trusting the client too much and the database rules are too broad, missing, or bypassed by an admin-style endpoint.

The most likely root cause is weak row-level access control on the backend, not a "mobile bug." The first thing I would inspect is the exact read path: which screen, which API call, which auth token, and which database rule allowed the wrong data to come back.

If customer data is leaking, I would treat that as a production safety issue first and a design issue second.

Triage in the First Hour

1. Confirm the blast radius.

  • Check whether leakage is happening for all users or only specific roles.
  • Verify whether it affects reads only, or also updates and deletes.
  • Look at support tickets, screenshots, and recent customer complaints.

2. Freeze risky changes.

  • Pause deployments from Expo or your CI pipeline.
  • Disable any admin automation that writes directly to shared tables.
  • If needed, temporarily restrict access to sensitive screens until rules are fixed.

3. Inspect auth flow in the app.

  • Review how the app stores tokens in Expo SecureStore or similar storage.
  • Confirm every request includes the correct user identity claim.
  • Check whether guest mode, test accounts, or cached sessions are mixing identities.

4. Review database policies and permissions.

  • Open the actual row-level security rules or equivalent access layer.
  • Check whether policies are using `true`, `is authenticated`, or other broad conditions.
  • Verify that every customer table filters by tenant ID or owner ID.

5. Check recent deploys and schema changes.

  • Review commits from the last 7 days.
  • Look for new tables used by automations, webhooks, queues, or background jobs.
  • Confirm no migration accidentally removed constraints or policy bindings.

6. Inspect logs and audit trails.

  • Look for cross-user reads in application logs.
  • Check whether service-role keys were used from client code by mistake.
  • Review failed auth attempts and unexpected spikes in read volume.

7. Validate exposed surfaces outside the app.

  • Review Cloudflare routes, API endpoints, and webhook handlers.
  • Check if any public endpoint returns internal IDs or full customer objects.
  • Confirm environment variables are not leaking into client bundles.
-- Quick sanity check pattern for tenant-scoped access
-- Replace with your actual table names and policy syntax
select *
from customer_records
where tenant_id = 'test-tenant-id'
order by created_at desc
limit 20;

Root Causes

| Likely cause | What it looks like | How I confirm it | |---|---|---| | Missing row-level security | Any authenticated user can read all rows | Inspect table policies and test with two different user accounts | | Broad policy condition | Policy uses `authenticated` without tenant filtering | Read policy expressions and compare against ownership model | | Service key used on client | App can query everything from Expo bundle | Search repo for admin keys in frontend code and build output | | Broken tenant mapping | Records have null or wrong `tenant_id` | Query for orphaned rows and compare against auth claims | | Over-permissive webhook handler | Automation writes data without checking caller identity | Review webhook auth headers and signature verification | | Cache poisoning or stale shared state | One user's data appears in another session after logout/login | Clear caches and reproduce on fresh device plus fresh account |

The most common failure in automation-heavy service businesses is shared infrastructure pretending to be multi-tenant-safe. A booking system, CRM sync, email automation flow, or internal dashboard often starts as one team's tool and later becomes customer-facing without proper isolation.

I also see founders assume "the mobile app controls what users see," but database rules are where enforcement must happen. If a bad request reaches the backend with a valid token but no tenant check, the backend will happily return someone else's data.

The Fix Plan

First, I would stop treating this as a UI patch. I would fix authorization at the data layer so even a buggy screen cannot leak records.

1. Lock down every sensitive table with explicit access rules.

  • Turn on row-level security or equivalent enforcement everywhere customer data lives.
  • Write allow rules per action: select, insert, update, delete.
  • Scope each rule to `user_id`, `account_id`, or `tenant_id`, never just "authenticated."

2. Standardize identity claims.

  • Make sure every token includes one stable tenant identifier.
  • Map users to accounts through a server-controlled record only.
  • Do not trust client-provided IDs for permission decisions.

3. Remove direct client access to privileged operations.

  • Move admin tasks into server-side functions or protected endpoints.
  • Keep service keys only on trusted backend environments.
  • If an automation needs elevated rights, make it verify signatures and caller context first.

4. Audit all automation entry points.

  • Webhooks should verify source signatures before writing anything.
  • Scheduled jobs should run with least privilege and target one account at a time where possible.
  • Background workers should log which tenant they touched so cross-account writes are visible fast.

5. Fix cached data handling in React Native and Expo.

  • Clear sensitive cache on logout and account switch.
  • Avoid storing full customer objects longer than needed on device.
  • Ensure query keys include tenant context so one user's cached response cannot render for another user.

6. Add a temporary deny-by-default fallback if needed.

  • If you cannot prove a table is safe today, block reads until it is fixed.
  • This may hurt some workflows for a few hours, but it prevents wider exposure and support escalation later.

7. Rotate secrets if exposure could include credentials or API keys.

  • Rotate any exposed database credentials immediately.
  • Rotate webhook secrets if logs suggest they were accessible outside trusted paths.
  • Update environment variables only after confirming deployment targets are correct.

My preferred order is: secure backend rules first, then patch automations, then clean up mobile caching. That sequence reduces breach risk fastest without breaking more of the system than necessary.

Regression Tests Before Redeploy

Before shipping anything back to production, I would run tests that prove isolation between customers under real conditions.

  • Create two test tenants with separate users and separate sample records.
  • Log in as Tenant A on one device and Tenant B on another device at the same time.
  • Confirm Tenant A cannot read Tenant B's records through:

1. normal screens, 2. search, 3. refresh, 4. deep links, 5. background sync, 6. offline rehydration after reconnecting.

Acceptance criteria:

  • Zero cross-tenant reads across all tested screens and APIs.
  • Zero privileged operations available from client-side code paths unless explicitly intended.
  • Logout clears sensitive local state within 1 second on device testing where possible.
  • No production secrets appear in Expo bundles, logs, crash reports, or browser devtools equivalents if applicable.

QA checks I would run:

  • Reproduce with a clean install on iOS simulator and Android emulator.
  • Test expired token behavior after force refresh and app relaunching.
  • Verify webhook retries do not duplicate data across tenants at p95 under load simulation of at least 50 requests per minute if that matches your business traffic profile more closely than lower test traffic does not reveal issues well enough here because these bugs hide behind normal usage patterns rather than obvious failures alone though I would still keep tests focused on realistic volumes rather than vanity load numbers alone
  • Check unauthorized requests return clear failures without exposing schema details.

If there is any AI-assisted automation inside the product flow:

  • Test prompt injection attempts against tool-use boundaries before redeploying agent features.
  • Verify assistants cannot retrieve records outside their assigned account context even if prompted directly to do so by malformed input or user content.

Prevention

I would put three guardrails around this class of failure: security checks before merge, runtime monitoring after deploy, and simpler UX around account boundaries.

Security guardrails:

  • Require code review for every policy change affecting authz logic.
  • Add tests for "user A cannot read user B" on every sensitive table change.
  • Scan repo history for accidental secret commits before release branches merge.

Monitoring guardrails:

  • Alert on unusual cross-account query patterns within 5 minutes of detection if possible.
  • Track denied requests separately from normal errors so real attacks do not get buried in generic logs .
  • Watch support volume for phrases like "wrong account," "someone else's data," or "switched profiles."

UX guardrails:

  • Make account switching explicit in navigation UI for multi-account users .
  • Show loading states while identity refreshes instead of briefly rendering stale cached data .
  • Add empty states that explain why some records are hidden rather than making users think data disappeared .

Performance guardrails matter too because slow auth flows create weird edge cases . If token refresh takes too long , users retry , caches race , and stale responses can appear . For mobile apps , I want auth-critical screens under p95 300 ms from cached session load to first usable state , even if heavier network calls take longer afterward .

A simple review checklist helps prevent repeat incidents:

  • Does this change touch permissions?
  • Does every query include tenant scope?
  • Are secrets stored server-side only?
  • Can an automation write outside its own account?
  • Can stale cache show another user's data?

When to Use Launch Ready

Use Launch Ready when you need production safety fixed fast , not when you want months of replatforming .

That makes sense when:

  • your product works but trust has been damaged ,
  • you need to ship safely before running ads ,
  • you have one bad release blocking revenue ,
  • your current setup has no clear owner ,
  • you need a clean handoff checklist so support does not spiral after launch .

What you should prepare before booking: 1. Repo access for mobile app plus backend . 2. Database console access . 3. Cloudflare / DNS / domain registrar access . 4. Email provider access . 5. Deployment platform access . 6. A short list of what data leaked , who saw it , and when it started .

If you can share screenshots of affected screens plus one example user journey that leaks data , I can usually narrow this down quickly . That saves time because I am not guessing across frontend noise while the real issue sits in permissions .

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. Expo SecureStore docs: https://docs.expo.dev/versions/latest/sdk/securestore/ 5. OWASP Authorization Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html

---

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.