fixes / launch-ready

How I Would Fix database rules leaking customer data in a Cursor-built Next.js client portal Using Launch Ready.

The symptom is usually simple and scary: one customer logs in and can see another customer's invoices, files, messages, or profile data. In a client...

How I Would Fix database rules leaking customer data in a Cursor-built Next.js client portal Using Launch Ready

The symptom is usually simple and scary: one customer logs in and can see another customer's invoices, files, messages, or profile data. In a client portal, that is not a "bug", it is a data exposure incident that can damage trust, trigger support escalation, and create legal risk.

The most likely root cause is bad authorization at the data layer, not just a broken UI. In Cursor-built Next.js apps, I usually find one of three problems first: overly broad database rules, server routes returning too much data, or client code calling the database directly without enforcing tenant ownership. The first thing I would inspect is the exact path from login to data fetch, starting with the database policy or query that returns the leaked record.

Triage in the First Hour

1. Freeze risky changes.

  • Pause deploys from Cursor or CI.
  • Stop any marketing traffic if the portal exposes sensitive records.

2. Confirm the scope.

  • Check which tables are exposed: users, invoices, documents, notes, support tickets, subscriptions.
  • Identify whether the leak affects all tenants or only certain roles.

3. Review auth logs.

  • Look for repeated requests from normal user accounts pulling records outside their org_id or user_id.
  • Check whether service account keys were used in browser code.

4. Inspect database rules or row-level security.

  • Open the policy files, SQL migrations, or dashboard rules.
  • Confirm whether reads are filtered by tenant_id, owner_id, or membership table.

5. Inspect Next.js data access paths.

  • Search for direct client-side calls to the database SDK.
  • Review server actions, API routes, and any `getServerSideProps` or route handlers returning broad queries.

6. Check build and deployment settings.

  • Verify that production env vars are not mixed with preview env vars.
  • Confirm that secrets are not exposed in `NEXT_PUBLIC_*` variables.

7. Look at recent Cursor-generated changes.

  • Review the last 3 to 5 commits for schema changes, policy edits, and query refactors.
  • Pay special attention to "temporary" admin bypasses and copied example code.

8. Inspect error monitoring and access logs.

  • Search for 401s and 403s that should have been blocked but were followed by successful reads.
  • Check for unusual spikes in payload size or record counts per request.

9. Reproduce with two test accounts.

  • Use Account A and Account B in separate tenants.
  • Verify whether Account A can request Account B's resource IDs through normal screens and direct API calls.

10. Preserve evidence.

  • Save screenshots, network traces, policy snapshots, and affected record IDs before changing anything.
## Quick diagnostic search for risky patterns
grep -R "supabase.from\|prisma.\|firebase\|directus\|fetch(" app src pages server actions api
grep -R "NEXT_PUBLIC_" .env* .

Root Causes

| Likely cause | What it looks like | How I confirm it | |---|---|---| | Missing row-level security | Any authenticated user can read rows from shared tables | Check DB policies and try cross-tenant reads with two accounts | | Over-broad query filters | Query uses `findMany()` or `select *` without tenant scoping | Review server routes and inspect generated SQL or logs | | Client-side direct database access | Browser code hits the database SDK with weak rules | Search for SDK usage in React components and network requests | | Bad ID-based access control | Portal trusts record IDs from the URL without ownership checks | Change IDs in dev tools and see whether another user's data loads | | Service role key exposure | Admin key appears in frontend env vars or shipped bundle | Search compiled assets and env files for privileged secrets | | Middleware/auth gap | Session exists but page/API never verifies tenant membership | Test protected routes after login with mismatched tenant context |

The Fix Plan

I would fix this in layers so we do not trade one leak for another.

1. Lock down the database first.

  • Turn on row-level security for every customer-facing table.
  • Add explicit read policies that require tenant ownership or membership through a join table.
  • Remove any blanket `allow read` rule used during prototyping.

2. Move privileged access out of the browser.

  • If the app needs admin-level reads, move them into server-only routes or server actions.
  • Keep service keys on the server only.
  • Never expose admin credentials in `NEXT_PUBLIC_*` variables.

3. Scope every query by tenant identity.

  • Every read should include `tenant_id`, `org_id`, `account_id`, or equivalent ownership check.
  • Do not rely on hidden UI state for security.
  • If a user can guess an ID, they must still get denied at the database layer.

4. Add authorization checks at the API boundary too.

  • Validate session first.
  • Verify membership before querying sensitive records.
  • Return 404 or 403 consistently so attackers cannot enumerate valid IDs.

5. Remove dangerous fallback logic.

  • Delete any "if no filter then return all" behavior.
  • Remove temporary debug endpoints that dump tables or raw JSON payloads.
  • Audit seed scripts so they do not reintroduce open permissions on deploy.

6. Rotate secrets after containment.

  • Rotate any leaked API keys immediately if there is any chance they reached the browser bundle or logs.
  • Regenerate DB credentials if privileged access was exposed.
  • Update deployment env vars and redeploy cleanly.

7. Add monitoring before reopening traffic fully.

  • Alert on cross-tenant access attempts, unusual response sizes, and repeated 403s on sensitive endpoints.
  • Log actor ID, tenant ID, route name, and resource type for every protected read.

8. Ship a minimal hotfix before cosmetic cleanup.

  • I would rather ship a narrow fix that blocks leakage than wait for a redesign of every portal screen.
  • After containment, I would schedule cleanup of duplicated queries and brittle auth code.

If I were handling this as Launch Ready work, I would keep scope tight: repair DNS-safe deployment settings if needed, verify environment variables are correct across preview and production, then ship the authorization fix behind a controlled release.

Regression Tests Before Redeploy

I would not redeploy until these pass in staging with real auth flows.

1. Cross-tenant read test

  • Account A cannot read Account B's invoices, files, messages, or profiles by UI or direct request.
  • Acceptance criteria: 0 successful unauthorized reads across 10 attempts.

2. Role-based access test

  • A basic client role cannot hit admin-only endpoints even if they know the URL.
  • Acceptance criteria: all forbidden routes return 403 or safe 404 consistently.

3. Session expiry test

  • Expired sessions must fail closed instead of falling back to public data paths.
  • Acceptance criteria: no data loads after token expiry; user is redirected to login.

4. Resource enumeration test

  • Changing record IDs in URLs must not reveal other customers' content.
  • Acceptance criteria: no sensitive payload returned for invalid ownership checks.

5. Server-side rendering test

  • Pages rendered on the server must respect tenant isolation too.

``` npm run test && npm run lint && npm run build ``` Acceptance criteria: build passes cleanly; no secret values appear in client bundles.

6. Logging review

  • Logs should show denied attempts without exposing PII or full payloads.
  • Acceptance criteria: log entries contain actor ID and route name only where needed.

7. Manual QA on mobile and desktop

  • Verify loading states do not briefly flash other users' records during hydration mismatch issues common in Next.js apps.
  • Acceptance criteria: no visible content flash of unauthorized data on refresh or navigation.

I would also set a release gate: no production deploy unless cross-tenant tests pass twice in a row from separate accounts and from both browser session plus API request paths.

Prevention

The best prevention is boring security discipline applied early enough to matter.

  • Code review guardrails:
  • Review every data fetch for ownership checks before style changes get attention.
  • Reject any PR that adds direct client-side access to sensitive tables without an explicit policy review.
  • Security guardrails:
  • Use least privilege everywhere possible.

-, Keep admin keys server-only.. -, Enforce rate limits on auth-sensitive endpoints.. -, Add CORS restrictions so random origins cannot probe your APIs..

  • QA guardrails:

-, Maintain two seeded test tenants.. -, Add regression tests for every new customer-facing table.. -, Run negative tests for invalid IDs,, expired sessions,, and role escalation attempts..

  • UX guardrails:

-, Show clear empty states when access is denied instead of ambiguous errors.. -, Make sure loading states do not reveal names,, counts,, or thumbnails before authorization completes.. -, Keep support links visible when users hit permission blocks so they do not spam your inbox..

  • Performance guardrails:

-, Index tenant_id,, owner_id,, created_at,, and foreign keys used by portal queries.. -, Watch p95 latency on protected endpoints; anything above 300 ms deserves profiling.. -, Cache safe public assets separately from private portal responses.. -, Avoid heavy third-party scripts on authenticated pages because they increase failure surface area..

When to Use Launch Ready

Use Launch Ready when you need me to turn a working but unsafe portal into something you can actually put customers behind without guessing about security gaps. This sprint fits best when you have a Cursor-built Next.js app that works locally but has weak auth boundaries,, broken deployment hygiene,, messy env vars,, missing monitoring,, or unclear production setup.

That is useful when you need fast containment plus a clean release path instead of another week of patching inside half-working code..

What I need from you before I start:

  • Repo access with recent commits visible
  • Database schema plus current rules/policies
  • Production and staging environment variable list
  • Two test accounts from different tenants
  • Screenshots or screen recording of the leak
  • Any monitoring dashboard links you already use

If you want me to handle it properly instead of guessing inside Cursor edits., book here: https://cal.com/cyprian-aarons/discovery

References

  • roadmap.sh Code Review Best Practices: https://roadmap.sh/code-review-best-practices
  • roadmap.sh API Security Best Practices: https://roadmap.sh/api-security-best-practices
  • roadmap.sh Cyber Security Roadmap: https://roadmap.sh/cyber-security
  • Next.js Security Docs: https://nextjs.org/docs/app/building-your-application/authentication
  • OWASP Cheat Sheet Series: https://cheatsheetseries.owasp.org/

---

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.