How I Would Fix database rules leaking customer data in a Cursor-built Next.js mobile app Using Launch Ready.
If customer records are showing up in the wrong account, the symptom is usually not 'the database is broken.' It is usually one of three things: broken...
How I Would Fix database rules leaking customer data in a Cursor-built Next.js mobile app Using Launch Ready
If customer records are showing up in the wrong account, the symptom is usually not "the database is broken." It is usually one of three things: broken auth context, overly broad read rules, or client code querying data without filtering by tenant or user.
The first thing I would inspect is the exact path from login to data fetch. In a Cursor-built Next.js mobile app, that means I want to see the auth session, the API route or client query, and the database rule that decides who can read what. If any one of those trusts the client too much, you get data exposure fast.
Triage in the First Hour
1. Check whether the leak is real or just a UI cache issue.
- Log in as two different users on separate devices.
- Confirm whether the wrong data appears in network responses or only on screen.
- If it is in the response payload, treat it as a security incident.
2. Inspect recent deploys and rule changes.
- Look at the last 3 commits.
- Check whether database rules, row-level security, API routes, or auth middleware changed.
- Note any "quick fix" made by Cursor without review.
3. Review server logs and request traces.
- Look for requests returning more rows than expected.
- Check whether user IDs, org IDs, or tenant IDs are missing from queries.
- Confirm whether unauthenticated or cross-tenant requests are being accepted.
4. Open the database rules directly.
- Read every allow/read policy tied to customer tables.
- Verify whether rules use `auth.uid()`, `tenant_id`, or equivalent ownership checks.
- Watch for wildcard reads like `true`, `public`, or `authenticated` without ownership constraints.
5. Inspect the mobile app network calls.
- In React Native, Flutter web wrappers, or Next.js mobile shells, confirm which endpoint returns customer data.
- Check whether filters are enforced server-side or only in client code.
- If filtering happens only in JavaScript, assume it can be bypassed.
6. Review environment variables and secrets handling.
- Confirm no service role key is shipped to the client bundle.
- Check `.env.local`, deployment settings, and any exposed build-time variables.
- Make sure production secrets are not reused in preview deployments.
7. Validate auth middleware and session boundaries.
- Ensure protected routes actually block anonymous access.
- Confirm session cookies are scoped correctly and not shared across tenants.
- Check for stale tokens after logout.
8. Capture evidence before changing anything.
- Save sample request/response pairs with redacted personal data.
- Screenshot affected screens.
- Record which accounts can see which records.
## Quick checks I would run grep -R "service_role\|auth.uid\|tenant_id\|select.*\*" . npm run build npm test
Root Causes
| Likely cause | What it looks like | How I confirm it | |---|---|---| | Overly permissive database rule | Any authenticated user can read all rows | Read policies allow `authenticated` without ownership checks | | Client-side filtering only | UI hides records but API still returns them | Network response includes other users' data | | Service key exposed in frontend | App can bypass normal authorization | Search bundles and env usage for admin keys | | Missing tenant scoping | Multi-user org data crosses accounts | Queries do not include `tenant_id` or `org_id` filters | | Broken auth middleware | Protected pages load before session is validated | Unauthed requests hit private endpoints successfully | | Cache returning stale private data | Old customer records appear after switching users | Disable cache and compare responses across sessions |
The most common root cause is a rule that was written for speed during prototyping. Cursor often helps founders ship fast, but it also makes it easy to accept a generated query or policy that looks right and is completely unsafe.
The Fix Plan
I would fix this in layers so we stop the leak first and then clean up the architecture.
1. Freeze risky deploys immediately.
- Pause production releases until access control is verified.
- Disable any preview environment that shares production data if needed.
- If there is active leakage, rotate exposed credentials first.
2. Move authorization to the server and database.
- Do not trust client-side filters for security decisions.
- Enforce ownership at query time using user ID or tenant ID checks.
- If you use Supabase, Firebase-like rules, Prisma plus custom API routes, or direct SQL access, make sure every read path has a hard access check.
3. Tighten database rules to least privilege.
- Replace broad read access with row-level constraints per user or tenant.
- Separate public content from private customer records into different tables if needed.
- Remove any policy that allows blanket access "for convenience."
4. Audit all endpoints that return customer data.
- Search for list endpoints, search endpoints, export endpoints, admin endpoints, and background jobs that touch customer records.
- Make each endpoint verify session identity before querying private rows.
- Reject requests with missing ownership context instead of guessing.
5. Remove secret exposure risk from the frontend bundle.
- Keep admin keys server-only.
- Use public keys only where they are designed to be public and safe by design.
- Rebuild after changing env vars so old values do not linger in deployed artifacts.
6. Add defensive logging without leaking PII.
- Log denied access attempts with user ID and route name only if compliant with your privacy policy.
- Do not log full customer payloads or tokens.
- Add alerting for sudden spikes in cross-account reads.
7. Patch caching behavior carefully.
- Disable shared caching on private responses unless you have explicit per-user cache keys.
- Mark sensitive responses as private where applicable.
- Make sure CDNs like Cloudflare are not caching authenticated JSON by mistake.
8. Ship one narrow fix at a time if possible.
- First fix access control at the source of truth: database rules or server query layer.
Then fix UI assumptions that depended on unsafe data shape. Then add tests so this does not regress.
A safe target here is simple: no private record should be returned unless both authentication and ownership checks pass. That should hold even if someone tampers with the client.
Regression Tests Before Redeploy
I would not redeploy until these pass:
1. Auth matrix test
- Anonymous user cannot read private customer rows
- User A cannot read User B's rows
- Tenant A cannot read Tenant B's rows
- Admin-only paths remain restricted
2. API contract test
- Private endpoints return only scoped records
- Missing session returns 401 or 403
- Invalid tenant context returns no data
3. Database policy test
- Direct queries obey row-level rules
- Wildcard reads are removed from sensitive tables
- Service-role-only operations stay server-side
4. Frontend behavior test
- Mobile screens do not show cached records after logout/login switch
- Loading states do not flash another user's content
- Empty states appear when access is denied
5. Security smoke test
- Inspect network calls for overbroad payloads
- Confirm no secret keys appear in client logs or bundles
- Verify CORS does not allow unintended origins for private APIs
6. Release acceptance criteria
- Zero cross-account reads in staging tests across 10 account pairs
- No failed auth bypass cases in CI
- Build passes with no secret scanning warnings
- p95 private-data endpoint latency stays under 300 ms after fixes
If this were my sprint, I would also run one manual exploratory pass on iPhone and Android-sized viewports because mobile apps often hide state bugs behind optimistic rendering and cached navigation.
Prevention
I would put guardrails around four areas: code review, monitoring, UX behavior, and deployment hygiene.
1. Code review guardrails
- Every query touching customer data must show its auth filter in code review diff form.
- Reject any change that moves authorization into client-only logic.
- Require at least one reviewer to check for tenant scoping and secret handling.
2. Monitoring guardrails
- Alert on unauthorized access attempts and unusual cross-account query patterns.
- Track 401/403 rates separately from 500s so auth failures do not get buried as generic errors.
- Watch p95 latency on protected endpoints because slow auth checks often lead teams to remove them later.
3. UX guardrails
- Show clear permission errors instead of silently loading partial private data from cache.
- Add explicit account switching states so users understand when they are viewing another workspace or profile boundary.
- Avoid auto-prefilling sensitive screens from stale local storage unless it is truly safe.
4. Performance guardrails
- Cache public content aggressively but keep private content scoped per user or disabled from shared caches when needed.
- Keep bundle size lean so auth guards execute quickly on mobile networks with poor connectivity.
- Measure LCP-like perceived load time for app shells because slow startup often leads founders to cut corners on security paths during refactors.
5. AI-assisted development guardrails
- Treat Cursor output as untrusted until reviewed against your authorization model.
- Maintain a short checklist for prompt-injected code changes: auth boundaries, secret usage, logging safety, and query scope expectations?
No code generation tool should be allowed to invent security logic without verification against live policies and tests.
When to Use Launch Ready
Use Launch Ready when you need me to stop guessing and turn this into a controlled production repair inside 48 hours.
For an app with leaking customer data, that matters because broken deployment hygiene often sits next to broken access control.
What I would ask you to prepare:
- Repo access for the Next.js app
- Database admin or policy editor access
- Hosting access such as Vercel or similar platform
- Cloudflare access if DNS sits there
- A short description of who should see which records
- One example of a leaked record pattern without full personal data
The best fit is when you have a working product that needs a fast rescue before more users sign up or ads spend starts burning money on a broken funnel. If your app already has paying users but trust is slipping because people can see each other's data, I would treat this as urgent rather than cosmetic.
References
1. Roadmap.sh Cyber Security Best Practices: https://roadmap.sh/cyber-security 2. Roadmap.sh API Security Best Practices: https://roadmap.sh/api-security-best-practices 3. Roadmap.sh Code Review Best Practices: https://roadmap.sh/code-review-best-practices 4. OWASP Top 10: https://owasp.org/www-project-top-ten/ 5. Next.js Security Docs: https://nextjs.org/docs/app/building-your-application/authentication
---
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.