How I Would Fix database rules leaking customer data in a React Native and Expo internal admin app Using Launch Ready.
If a React Native and Expo internal admin app is leaking customer data, I treat it as a production security incident, not a UI bug. The usual symptom is...
Opening
If a React Native and Expo internal admin app is leaking customer data, I treat it as a production security incident, not a UI bug. The usual symptom is simple: an admin screen shows records it should not, or a user can infer other customers' data through list views, filters, exports, or cached responses.
The most likely root cause is weak database rules or overbroad access from the client. The first thing I would inspect is the exact path from app screen to database query: auth state, role claims, row-level rules, and whether the app is reading directly from the database with a public key or token that grants too much access.
Triage in the First Hour
1. Freeze risky changes.
- Pause deployments.
- Disable any automation that could sync, export, or email customer records.
- If there is active leakage, rotate exposed keys first.
2. Check the affected screens.
- Open the admin flows where data appears.
- Confirm whether leakage happens in list pages, search, filters, detail pages, CSV export, or offline cache.
3. Inspect logs and alerts.
- Review auth logs for unusual reads.
- Check database audit logs for broad table scans.
- Look at Sentry, Datadog, Logtail, or your cloud logs for repeated permission failures or unexpected 200 responses.
4. Verify current auth context.
- Confirm which role is attached to the test account.
- Check whether claims are stale after role changes.
- Confirm session refresh behavior in Expo.
5. Inspect the database rules.
- Review row-level security policies or equivalent access rules.
- Look for "allow read if authenticated" style logic.
- Check for missing tenant filters or ownership checks.
6. Review client code paths.
- Search for direct table queries from the app.
- Check if any admin screens use a shared service account key in the bundle.
- Verify whether secrets are stored in Expo public env vars by mistake.
7. Validate recent builds.
- Compare the last safe build with the current one.
- Check if a new filter, export feature, or cache layer changed access behavior.
8. Inspect storage and caches.
- Review local persistence in AsyncStorage, SecureStore, SQLite, or persisted query caches.
- Make sure old data is not being shown after logout or role change.
A quick diagnostic command I would run on the backend side:
grep -R "select .*from\|from(\".*\")\|supabaseKey\|service_role\|adminKey" src app .
That usually exposes whether the app is querying sensitive tables too broadly or shipping privileged credentials into the client.
Root Causes
1. Row-level security is missing or too broad This is the most common failure. A policy like "authenticated users can read all rows" will leak customer data immediately.
How I confirm it:
- Review every policy on customer tables.
- Test with two accounts from different tenants or roles.
- Run direct reads against the API with a low-privilege token and confirm what returns.
2. The app uses a privileged key in the client If a service role key or admin secret ships inside React Native code or Expo config exposed to the bundle, any user can extract it.
How I confirm it:
- Search built artifacts and source for service keys.
- Inspect Expo env vars and build-time constants.
- Decompile or scan the JS bundle if needed to verify no secret leaked into client code.
3. Tenant scoping is missing in queries The database may be protected in theory, but queries still return too much because every request lacks tenant_id or owner_id filtering.
How I confirm it:
- Trace each query from screen to API to database.
- Compare requests made by two different tenants.
- Check whether search and pagination endpoints ignore tenant scope.
4. Cached data survives logout or role changes An internal admin may log out and another person logs in on the same device. If cached results are not cleared, old customer data stays visible.
How I confirm it:
- Reproduce on one device with multiple accounts.
- Clear app storage manually and see if leakage disappears.
- Review cache invalidation on sign-out and role switch events.
5. Backend endpoints bypass database rules Sometimes an API route was added "for convenience" and reads directly from the database using elevated credentials without enforcing authorization again.
How I confirm it:
- Inspect server routes used by admin screens.
- Check whether authorization happens before each fetch.
- Compare endpoint behavior with direct database access rules.
6. Misconfigured previews or test environments are pointing at production I have seen internal apps leak because staging builds pointed at live production data with relaxed rules.
How I confirm it:
- Check environment variables per build channel.
- Verify base URLs for dev, staging, and production.
- Confirm that preview builds cannot hit production secrets or datasets unless explicitly intended.
The Fix Plan
My approach is to stop leakage first, then tighten access without breaking legitimate admin workflows.
1. Contain exposure immediately
- Disable any broad read policies that expose customer tables.
- Rotate any keys that may have been embedded in Expo config or client bundles.
- If needed, temporarily route sensitive views through a server endpoint with strict auth while policies are repaired.
2. Lock down data access at the database layer
- Add row-level security on all customer-facing tables.
- Require tenant_id matching plus role checks for internal admin access.
- Deny by default and add explicit allow rules only where needed.
3. Remove privileged access from React Native
- Make sure the app only uses public client keys where appropriate.
- Move sensitive reads to authenticated backend endpoints if direct client reads are too risky for this product stage.
- Never ship service-role credentials inside Expo bundles.
4. Enforce authorization twice
- Check permissions in both the backend route and database rule set.
- Do not trust frontend role flags alone because they can be modified locally.
- Revalidate claims on session refresh and after role updates.
5. Clean up caching and session handling
- Clear persisted query caches on logout and account switch.
- Invalidate stale tokens after permission changes.
- Ensure offline storage does not retain sensitive records longer than necessary.
6. Add safe observability
- Log denied reads without logging raw customer payloads.
- Add alerts for unusual cross-tenant read patterns and spikes in permission failures over 15 minutes.
- Track who accessed what record and when for audit purposes.
7. Patch release process
- Ship as a small hotfix branch only.
- Avoid feature work until access control passes review and tests pass cleanly across dev and staging environments.
Here is how I would think about it operationally:
If this were my sprint, I would keep scope tight: fix access control first, then verify every admin screen against least privilege before touching UX polish.
Regression Tests Before Redeploy
I would not redeploy until these pass:
1. Role-based read tests
- Admin can read only allowed tenant records.
- Non-admin cannot read other tenants' records at all.
- A revoked user loses access after token refresh.
2. Cross-account tests
- Sign in as user A, then user B on same device.
- Confirm no cached customer data remains visible after logout/login cycles.
3. Negative API tests
- Requests without auth fail with 401 or equivalent.
- Requests with wrong tenant context fail with 403 or equivalent if that matches your design.
4. Export and search tests
- CSV export contains only permitted rows.
- Search never returns records outside scope through pagination edge cases or fuzzy matching bugs.
5. Build verification
- Test iOS and Android release builds from Expo EAS output if that is your shipping path.
- Confirm environment variables resolve correctly per environment channel only.
6. Acceptance criteria I would use
- Zero unauthorized rows visible across three test accounts from different roles/tenants.
- No privileged secrets present in bundled JS output or public config files checked into git history after remediation plan starts moving forward where rotation is required to protect live systems quickly enough to avoid hours of exposure risk rather than days of uncertainty about who could have accessed what during that window while you investigate logs carefully enough to avoid false confidence from partial evidence alone; yes this sounds strict because security incidents deserve strictness before redeploying anything again into production traffic ever until verified properly under realistic conditions with fresh tokens plus cleared caches plus repeatable test evidence collected carefully enough to stand up during an incident review later on when stakeholders ask why this happened at all and what changed since last week
Prevention
I would put guardrails around four layers: database, code review, QA, and monitoring.
1. Database guardrails
- Keep row-level security enabled by default on sensitive tables.
- Use least privilege roles for every service account.
- Review policies whenever schema changes touch customer data fields.
2. Code review guardrails
- Block merges that add direct privileged reads from mobile clients without approval from someone who understands auth boundaries well enough to spot hidden escalation paths quickly during review rather than after launch damage spreads further than expected across customers who should never have seen each other's records in the first place anymore once proper controls exist again now going forward permanently ideally
3. QA guardrails
- Add security-focused test cases to every release checklist: wrong tenant, expired token, revoked role, stale cache, offline mode restore."
- Run regression tests on both iOS and Android release builds before shipping hotfixes."
4. Monitoring guardrails"
- Alert on spikes in denied reads."
- Track unusual record volume per user."
- Audit exports separately because exports often become silent data leaks."
5.'UX guardrails' -"Hide sensitive fields unless they are truly needed." -"Make loading states explicit so users do not retry requests aggressively." -"Show clear permission errors instead of exposing partial data."
6.'Performance guardrails' -"Keep admin queries narrow so you do not trade security fixes for slow screens." -"Aim for p95 API latency under 300 ms for list views." -"Use indexed tenant filters so protection does not create support tickets about sluggish dashboards."
When to Use Launch Ready
Launch Ready fits when you need this fixed fast without turning your team into incident responders for a week.
What you should prepare before booking: - Access to your repo.. - Expo/EAS project access.. - Database console/admin panel.. - Hosting provider.. - Cloudflare.. - Any error screenshots.. - A short list of which roles should see which records..
If you already know customer data has leaked across tenants,, do not wait for a full redesign.. Book time immediately at https://cal.com/cyprian-aarons/discovery so I can scope whether this needs a hotfix sprint,.a backend authorization rewrite,.or both..
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., Roadmap.sh Code Review Best Practices: https://roadmap.sh/code-review-best-practices 4., Supabase Row Level Security Docs: https://supabase.com/docs/guides/database/postgres/row-level-security 5., Expo Environment Variables: https://docs.expo.dev/guides/environment-variables/
---
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.