How I Would Fix database rules leaking customer data in a React Native and Expo client portal Using Launch Ready.
If a React Native and Expo client portal is exposing customer data, I treat it as a production incident first and a code issue second. The symptom is...
How I Would Fix database rules leaking customer data in a React Native and Expo client portal Using Launch Ready
If a React Native and Expo client portal is exposing customer data, I treat it as a production incident first and a code issue second. The symptom is usually simple: one user can see another user's records, attachments, invoices, or profile fields after login.
The most likely root cause is weak database authorization, not the mobile app itself. The first thing I would inspect is the data access path end to end: auth claims, database rules, API queries, and any admin keys or service roles bundled into the Expo client.
Triage in the First Hour
1. Confirm the leak with two test accounts.
- Use two real user roles or seeded test users.
- Check whether User A can load User B's records from the app, API, or cached state.
2. Freeze risky deploys.
- Pause new releases from EAS, CI, or manual publishing.
- If the leak is active in production, disable any feature flag that exposes shared lists or admin views.
3. Inspect auth and database logs.
- Look for cross-user reads on customer tables.
- Check whether requests are using user-scoped tokens or a server/service credential.
4. Review the last 3 changes.
- App screens that fetch portal data.
- Database rules or row-level security updates.
- Backend endpoint changes, especially anything added for speed.
5. Audit secrets and environment variables.
- Verify no service role key is inside the Expo bundle.
- Check EAS secrets, build profiles, and runtime config.
6. Check cloud dashboards.
- API gateway logs
- Database audit logs
- Error tracking
- Uptime and alerting
- CDN or proxy logs if requests pass through Cloudflare
7. Inspect the affected screens.
- Customer list
- Detail page
- Search results
- Cached offline views
- Deep links that open a record directly
8. Reproduce on a clean build.
- Install the latest production APK/IPA or TestFlight build.
- Sign in with two separate accounts and test every route that loads customer data.
A simple query pattern check often reveals the issue fast:
-- Example diagnostic check for row-level access patterns select id, customer_id, created_by from portal_records where customer_id <> auth.uid();
If this returns rows during a user-scoped request path, you have an authorization problem somewhere in rules, queries, or backend enforcement.
Root Causes
1. Missing row-level security or equivalent authorization.
- Confirm by checking whether tables are readable without a user-scoped filter.
- In Postgres-backed stacks, verify RLS is enabled and policies exist for every table with customer data.
2. Over-permissive read policies.
- Confirm by reviewing policies that use broad conditions like `true`, `authenticated`, or role checks without ownership checks.
- Test whether a logged-in user can read rows they do not own.
3. Service role key exposed to the client.
- Confirm by searching the Expo codebase and build config for admin keys or secrets that should only live server-side.
- If the app can bypass user rules entirely, this is a major incident.
4. Backend endpoint missing authorization checks.
- Confirm by calling the API with two different user tokens and comparing returned records.
- If the endpoint trusts only record IDs and not ownership, it will leak data even if the database is locked down.
5. Client-side caching mixing users' data.
- Confirm by logging out one account and logging into another on the same device.
- If old records still appear before refresh, stale cache keys may be scoped incorrectly.
6. Shared identifiers in query design.
- Confirm by checking whether queries use email alone, mutable profile fields, or public IDs that are easy to guess.
- Good access control should be based on immutable authenticated identity plus explicit ownership mapping.
The Fix Plan
My approach is to stop the leak first, then tighten every layer so it cannot come back through another path. I would not "patch one screen" and hope for the best.
1. Lock down database access at the source.
- Enable row-level security on every table containing customer data.
- Add explicit read/write policies per table based on authenticated user ID and ownership relation.
- Remove any broad allow rules that were added during prototyping.
2. Move privileged access out of Expo immediately.
- Remove service role credentials from client code and client-side environment files.
- Put admin-only operations behind a server function, edge function, or backend endpoint with strict auth checks.
3. Rebuild query boundaries around identity.
- Every request should resolve to one authenticated subject before querying records.
- Use server-side filters for tenant ID or owner ID rather than trusting client-supplied IDs alone.
4. Clean up cached state handling in the app.
- Scope cache keys by authenticated user ID and tenant ID where relevant.
- Clear sensitive caches on logout and account switch.
- Disable any offline persistence for highly sensitive portal records unless it has been reviewed carefully.
5. Add defensive response shaping.
- Return only fields needed for each screen.
- Do not send internal notes, payment metadata, staff-only fields, or cross-tenant identifiers to the mobile app unless required.
6. Rotate secrets if exposure is possible.
- Rotate any leaked API keys, database credentials, signing keys, or webhook secrets immediately.
- Review who had access to builds containing those values.
7. Add monitoring before shipping again.
- Alert on unusual cross-tenant reads, elevated 403s after policy changes, auth failures, and admin endpoint usage spikes.
8. Ship as a controlled release window only after verification. \- I would deploy behind a feature flag or staged rollout if traffic allows it.\n \- For a portal with active customers, I want rollback ready within 10 minutes."
Regression Tests Before Redeploy
I would not redeploy until these checks pass in staging against production-like data shapes with sanitized fixtures:
- Two-user isolation test
- User A cannot read User B's records through UI navigation or direct API calls.
- Direct object access test
- Paste another customer's record ID into deep links and confirm access is denied with a clean error state.
- Logout/login cache test
- Log out of Account A, log into Account B on the same device, and confirm no stale private data appears.
- Role boundary test
- Verify customer role cannot see staff/admin fields anywhere in responses or screens.
- Negative auth test
- Remove token entirely and confirm protected endpoints return 401 or 403 consistently.
- Build integrity test
- Scan Expo bundles and EAS envs to confirm no secret keys are embedded in shipped assets.
- Observability check
- Verify logs capture auth outcome without storing sensitive payloads in plain text.
Acceptance criteria I would use:
- Zero cross-user reads across at least 20 seeded accounts per tenant group.
- All protected endpoints return correct auth errors within p95 under 300 ms in staging.
- No secret values found in client bundles or public config files.
- Logout clears sensitive cached portal state completely on iOS and Android simulator builds.
Prevention
The best prevention here is boring discipline around authorization review before release. For client portals specifically, I care more about access control than UI polish because one bad rule can expose every customer record at once.
What I would put in place:
- Code review gates
- Any change touching queries, policies, auth middleware, or secrets gets senior review before merge.
- Security checklist
- Every release should answer: who can read this row? who can write it? what happens if token validation fails?
- Automated tests
- Add regression tests for tenant isolation and direct object access so this does not rely on memory.
- Logging discipline
- Log access decisions and request IDs; do not log private payloads or tokens.
- Secret management
- Keep all privileged credentials server-side only and rotate them on every suspected exposure event.
- UX guardrails
- Show clear empty states when access is denied instead of leaking hints like "record exists but unavailable."
- Performance guardrails
- Keep authorization checks fast enough that developers do not bypass them later because they feel slow; aim for p95 under 300 ms on protected reads with proper indexes.
Here is how I think about it:
When to Use Launch Ready
Launch Ready fits when you need this fixed fast without turning your product into a long consulting project.
For this specific failure mode,I would use Launch Ready when:
- The app works,but production safety is broken,
- You need an immediate deployment cleanup after fixing authorization,
- You want monitoring in place before users keep hitting the portal,
- You need confidence that secrets are out of client builds,
- You want a clean handoff instead of another fragile patch cycle,
What you should prepare before booking:
- Repository access plus current branch status,
- Expo/EAS account access,
- Database admin access,
- Hosting/CDN access,
- List of affected screens,tables,and roles,
- A few test accounts showing different permission levels,
- Any recent incident notes,support tickets,and screenshots,
My goal in that sprint is simple: stop leakage,rebuild trust boundaries,and leave you with a safer deployment path than what you had before..
References
- https://roadmap.sh/api-security-best-practices
- https://roadmap.sh/cyber-security
- https://roadmap.sh/qa
- https://docs.expo.dev/
- https://supabase.com/docs/guides/database/postgres/row-level-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.