How I Would Fix database rules leaking customer data in a Cursor-built Next.js client portal Using Launch Ready.
If a client portal is leaking customer data, I treat it as a production security incident, not a bug. The usual symptom is simple: one signed-in user can...
Opening
If a client portal is leaking customer data, I treat it as a production security incident, not a bug. The usual symptom is simple: one signed-in user can see another user's records, attachments, invoices, or profile fields.
In a Cursor-built Next.js app, the most likely root cause is weak authorization at the data layer, not just a bad UI check. The first thing I would inspect is the exact path from browser request to database query: page route, API route or server action, auth session, and the database rule or query filter that decides which rows are returned.
Triage in the First Hour
1. Freeze risky changes.
- Pause deploys from Cursor or any connected CI until I know where the leak is coming from.
- If possible, switch the portal to read-only mode for affected areas.
2. Confirm the blast radius.
- Check which tables or collections are exposed.
- Identify whether the leak affects all users, one tenant, one role, or only certain pages.
3. Inspect auth logs.
- Review sign-in events, session creation, token refresh failures, and unusual access patterns.
- Look for requests where user A fetched records belonging to user B.
4. Check database rules or policies.
- Review row-level security policies, Firestore rules, Supabase policies, Prisma queries with missing filters, or custom API authorization checks.
- Confirm whether access is enforced at the database layer or only in the frontend.
5. Review recent commits and Cursor-generated edits.
- Diff the last 5 to 10 changes around auth, data fetching, and route handlers.
- Look for "temporary" admin bypasses, hardcoded IDs, or copied example code.
6. Inspect deployed environment variables.
- Verify `NEXT_PUBLIC_` variables are not exposing secrets.
- Confirm production and preview environments are not pointing at the same database by mistake.
7. Check build and runtime logs.
- Look for failed auth checks being swallowed and replaced with fallback queries.
- Review serverless function logs for errors that may have caused unsafe fallback behavior.
8. Validate from two accounts.
- Use two test users with different organizations.
- Reproduce the issue in production-like conditions before changing anything.
A quick diagnostic command I often use during triage is:
git diff HEAD~5..HEAD -- app/ lib/ src/ prisma/ supabase/ firestore/
That usually shows whether someone changed authorization logic in a place that controls every request.
Root Causes
| Likely cause | What it looks like | How I confirm it | |---|---|---| | Missing row-level security | Users can query rows by guessing IDs | Check DB policies and compare results across two accounts | | Frontend-only authorization | UI hides data but API still returns it | Call the API directly with a valid session and inspect payloads | | Bad tenant filter | Queries return all records instead of one org's records | Review `where` clauses and tenant ID propagation | | Overbroad service role usage | Server code uses admin credentials for normal reads | Search for service keys in route handlers and server actions | | Broken ownership mapping | User session does not map correctly to org/user record | Trace session user ID to database ownership fields | | Preview/prod environment mix-up | Test data appears in prod or prod data appears in preview | Compare env vars and database connection strings |
The most common failure in AI-built apps is that someone trusted the UI too much. That is not enough for a client portal because direct requests, stale sessions, and copied URLs will bypass visual restrictions fast.
The Fix Plan
I would fix this in layers so we stop the leak first and then make sure it cannot come back.
1. Lock down data access at the database layer.
- Turn on row-level security if your stack supports it.
- Write policies so each row must match `user_id`, `org_id`, or `tenant_id` tied to the authenticated session.
- Deny by default. Then add explicit allow rules only where needed.
2. Remove any admin credential from normal reads.
- Service roles should be reserved for internal jobs, migrations, and controlled backend tasks.
- If a server action needs elevated access, I isolate it behind strict authorization checks first.
3. Move authorization into server-side code paths.
- Next.js client components should never be trusted to enforce access control alone.
- I would verify every route handler and server action checks identity before returning data.
4. Normalize ownership fields.
- Every protected record needs a clear owner reference: user ID, account ID, or organization ID.
- If records are shared across teams, define that relationship explicitly instead of inferring it from email or display name.
5. Patch dangerous fallback behavior.
- If auth fails or claims are missing, return an error instead of "best effort" data.
- Silent fallback logic is how portals leak records without anyone noticing until a customer complains.
6. Rotate secrets if exposure is possible.
- If service keys were used incorrectly or logged anywhere, rotate them immediately.
- Update environment variables in all deployments and preview environments.
7. Add defensive logging without leaking PII.
- Log denied access attempts with request ID, user ID hash, route name, and reason code.
- Do not log full customer payloads into application logs.
8. Deploy in a controlled sequence.
- First patch policy rules in staging with production-like data shape.
- Then deploy backend fixes before frontend cleanup so there is no window where UI assumptions outpace enforcement.
My rule here is simple: if one layer says "no" but another layer still says "yes," I trust the stricter layer only after I confirm it is actually enforced everywhere.
Regression Tests Before Redeploy
Before shipping anything back to production, I want proof that unauthorized access fails consistently and authorized access still works.
- Two-user isolation test
- User A must never see User B's records through UI routes or direct API calls.
- Acceptance criteria: 0 cross-tenant records returned across 20 repeated requests.
- Direct request test
- Hit protected endpoints with valid sessions from different accounts.
- Acceptance criteria: unauthorized responses return 401 or 403, never partial customer data.
- Role-based access test
- Verify admin users can see only what their role permits.
- Acceptance criteria: each role sees exactly its allowed dataset and nothing more.
- Empty state test
- Confirm denied pages do not reveal whether hidden records exist through counts or metadata leaks.
- Acceptance criteria: response messages stay generic and do not expose record names or totals unless authorized.
- Session expiry test
- Expire tokens and retry navigation after idle timeouts.
- Acceptance criteria: expired sessions redirect cleanly to login without loading protected content first.
- Audit log test
- Confirm denied attempts are recorded safely for review later.
- Acceptance criteria: logs include traceable metadata but no customer PII.
- Smoke test on critical flows
- Open dashboard, invoices, documents, messages, settings, and downloads for two separate users.
- Acceptance criteria: no cross-account visibility anywhere in those flows after redeploy.
I would also require at least one targeted manual QA pass on mobile because client portals often hide broken states behind responsive layouts. A secure portal that leaks on desktop but looks fine on mobile still fails the business because support tickets will come fast either way.
Prevention
The fix should not depend on memory or good intentions. I would put guardrails around security review so this does not reappear in the next Cursor-generated change set.
- Code review guardrails
- Every protected query must show where tenant scoping happens.
- Reject PRs that use broad selects without an explicit ownership filter.
- Security checklist
- Review authentication, authorization, input validation, secret handling, CORS policy, rate limits, dependency risk, and least privilege before merge.
- Any new endpoint handling customer data gets an approval gate from someone who understands access control.
- Monitoring
- Alert on unusual spikes in denied requests or cross-tenant lookup failures.
- Track p95 latency too; broken auth middleware often adds slowdowns when implemented badly. For most portals I want protected page loads under 300 ms p95 after cache warmup.
- UX guardrails
- Show clear permission errors instead of blank screens or ambiguous loading states that confuse users into retrying broken paths repeatedly.
- Good UX reduces support load when access changes after account updates or billing issues.
- Performance guardrails
- Avoid re-fetching entire datasets on every navigation if you only need one account scope at a time; this cuts exposure surface as well as load time. For client portals I usually target Lighthouse scores above 90 on key authenticated pages once images and scripts are cleaned up properly.
- Testing discipline
- Keep one automated test per sensitive endpoint that proves unauthorized access fails by default. This should be part of CI so a future refactor cannot quietly reopen the leak.
If you are using AI-assisted coding heavily inside Cursor, I would also add prompt hygiene rules: do not let generated code invent its own auth model unless a human reviews it against your actual database schema and session model first.
When to Use Launch Ready
Launch Ready fits when you already have a working portal but need me to make it production-safe fast.
I would use this sprint if:
- Your Next.js app works locally but breaks under real traffic,
- You need production deployment cleaned up before customers start using it,
- You suspect secrets or auth config are messy,
- You want monitoring live before another incident hits,
- You need a fast handoff instead of another week of trial-and-error fixes..
What you should prepare:
- Repository access,
- Hosting account access,
- Domain registrar access,
- Database dashboard access,
- Auth provider access,
- A list of sensitive pages and roles,
- One example of how data should be scoped per user or organization..
If your issue includes customer-data leakage plus shaky deployment hygiene., Launch Ready gives me enough room to stabilize the launch stack while I patch the security path that caused the incident..
References
1. Roadmap.sh API Security Best Practices https://roadmap.sh/api-security-best-practices
2. Roadmap.sh Code Review Best Practices https://roadmap.sh/code-review-best-practices
3. Supabase Row Level Security docs https://supabase.com/docs/guides/database/postgres/row-level-security
4. Next.js Authentication docs https://nextjs.org/docs/app/building-your-application/authentication
5. OWASP Access Control Cheat Sheet https://cheatsheetseries.owasp.org/cheatsheets/Access_Control_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.*
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.