How I Would Fix database rules leaking customer data in a Next.js and Stripe client portal Using Launch Ready.
If customer records are showing up in the wrong portal session, I treat that as a production security incident, not a UI bug. The most likely root cause...
How I Would Fix database rules leaking customer data in a Next.js and Stripe client portal Using Launch Ready
If customer records are showing up in the wrong portal session, I treat that as a production security incident, not a UI bug. The most likely root cause is weak authorization around the database query layer, usually a missing tenant filter, broken row-level security, or client-side code that trusts Stripe session data too much.
The first thing I would inspect is the exact path from login to data fetch: Next.js route, API handler, database query, and any Stripe customer lookup. I want to prove whether the leak happens because the app is asking for too much data, or because the database is returning data it should never have exposed.
Triage in the First Hour
1. Check live access logs for the affected portal routes.
- Look for repeated requests to customer-specific endpoints.
- Confirm whether one account can request another account's records by changing an ID or slug.
2. Inspect the auth layer in Next.js.
- Verify how the session is created.
- Confirm whether user identity comes from server-side session state, not from a query string or browser-stored ID.
3. Review Stripe customer mapping.
- Check how `stripeCustomerId` is stored.
- Confirm that each authenticated user maps to exactly one internal tenant or account record.
4. Open the database rules or RLS policies.
- Look for broad read policies like `allow read: if true`.
- Check whether policy conditions include `user_id`, `account_id`, or `tenant_id`.
5. Inspect recent deploys and migrations.
- Identify any schema change that widened access.
- Check whether a new index, view, or API route bypassed existing rules.
6. Review server logs and error traces.
- Search for queries returning more rows than expected.
- Look for 200 responses with suspiciously large payloads.
7. Audit environment variables and secrets handling.
- Confirm Stripe secret keys are only on the server.
- Make sure no privileged database credentials are exposed to the browser bundle.
8. Check monitoring and alerting.
- Review uptime alerts, auth failures, and unusual response sizes.
- If there is no alerting yet, note that as part of the fix scope.
## Quick sanity checks I would run on a Next.js portal grep -R "stripeCustomerId\|accountId\|tenantId\|getServerSideProps\|route.ts" app pages lib
Root Causes
1. Missing row-level security or weak database rules
- Symptom: users can read rows that belong to other accounts.
- How I confirm it: I test two different authenticated users against the same endpoint and compare returned records. If user A can see user B's row by guessing an ID, the rule is broken.
2. Client-side fetching with an exposed identifier
- Symptom: browser code calls `/api/customer?id=123` or queries directly with a predictable key.
- How I confirm it: I inspect network requests in DevTools and check whether access depends on an ID passed from the browser instead of derived from server session state.
3. Broken tenant scoping in API routes
- Symptom: API returns all portal records because the filter is missing or applied after fetch.
- How I confirm it: I review route handlers for queries like `findMany()` without `where: { tenantId: session.user.id }`.
4. Stripe customer lookup used as authorization
- Symptom: possession of a Stripe customer ID grants access to portal content.
- How I confirm it: I trace where `stripeCustomerId` enters the request flow. If it is used as proof of identity instead of just billing linkage, that is a design flaw.
5. Overprivileged service role key
- Symptom: server functions use admin credentials everywhere, so any bug becomes a data exposure.
- How I confirm it: I search for service-role usage outside tightly controlled server-only paths and verify whether those calls enforce ownership checks before returning data.
6. Misconfigured caching or static rendering
- Symptom: one user's private page gets cached and served to another user.
- How I confirm it: I inspect Next.js caching behavior, CDN headers, and whether private pages are accidentally statically rendered or cached at the edge.
The Fix Plan
My goal is to stop the leak first, then tighten everything around it without breaking billing or support workflows.
1. Freeze risky paths immediately.
- Disable any endpoint that returns customer records without verified ownership checks.
- If needed, temporarily gate portal access while keeping billing pages online.
2. Move authorization to the server boundary.
- Every request must derive identity from session state on the server.
- Never trust `customerId`, `accountId`, or email passed from the browser as proof of access.
3. Enforce tenant isolation in one place.
- Add a single source of truth for ownership checks in middleware, API handlers, or database policies.
- In most cases, I prefer defense in depth: server-side auth plus database-level restrictions.
4. Tighten database rules or RLS policies.
- Allow reads only when `row.account_id = auth.account_id` or equivalent logic matches your model.
- Deny by default if there is no verified match.
5. Separate billing identity from portal authorization.
- Stripe should tell you who paid, not who can read private data by itself.
- Store Stripe IDs as linked metadata after authentication, not as standalone access tokens.
6. Lock down Next.js rendering and caching.
- Mark private routes as dynamic where needed.
- Set private cache headers on authenticated pages and disable public CDN caching for sensitive responses.
7. Rotate secrets if exposure is possible.
- Rotate any leaked keys immediately.
- Reissue environment variables and verify they are only present on trusted servers.
8. Add audit logging before redeploying.
- Log who requested what record and when.
- Keep logs free of raw payment details and sensitive personal data.
9. Patch with small changes first.
- Do not rewrite the portal during an incident response sprint.
- The safest move is narrow fixes around auth boundaries and query filters.
A simple rule helps here: if a user can change one value in the browser and see another customer's data, that path is still unsafe.
Regression Tests Before Redeploy
I would not ship this fix until these checks pass:
1. Cross-account access test
- Acceptance criteria: User A cannot view User B's invoices, subscriptions, files, tickets, or profile fields under any route.
2. Session tampering test
- Acceptance criteria: Changing URL params, cookies, local storage values, or request payloads does not expand access.
3. Unauthorized direct request test - Acceptance criteria: Requests made without an authenticated session return 401 or 403 consistently.
4. Cache isolation test - Acceptance criteria: Private pages do not appear in shared cache responses or browser history previews after logout.
5. Billing integrity test - Acceptance criteria: Legitimate Stripe-linked users still see their own payment status, invoices, and plan details correctly.
6. Error handling test - Acceptance criteria: Failed auth returns a safe message without exposing internal IDs or stack traces.
7. Security smoke test - Acceptance criteria: No endpoint returns more than one tenant's records unless explicitly intended for staff roles with audit logging enabled.
8. Manual exploratory pass - Acceptance criteria: I can log in as two different users and never see cross-tenant content across desktop and mobile flows.
I usually want at least 80 percent coverage on ownership checks for critical portal endpoints before calling this stable enough to deploy again.
Prevention
The real fix is not just patching one bad query. It is building guardrails so this class of issue does not come back during future feature work.
- Put authorization reviews into code review every time someone touches portal routes or database rules.
- Require tests for ownership boundaries on all customer-facing endpoints.
- Add alerting for unusual response sizes, repeated forbidden requests, and unexpected spikes in private page hits from one IP range.
- Log security events with account context so support can investigate without exposing sensitive content in logs.
- Use least privilege everywhere:
admin keys only where needed, read-only roles for reporting, no direct browser access to privileged databases, no shared service credentials across unrelated systems.
- Keep private pages dynamic unless you have deliberately designed safe caching behavior around them.
- Run periodic red-team style checks against prompt injection if AI features ever touch support docs or customer records later on because leaked tools often become leaked data paths too.
From a UX angle, make failure states clear but boring: "Your session expired" is better than "Something went wrong." That reduces support load and stops users from retrying broken flows in ways that create more risk.
For performance hygiene:
- keep private queries indexed on `account_id` and `created_at`,
- watch p95 latency on portal endpoints,
- avoid loading full customer tables when one record will do,
- keep third-party scripts off authenticated pages unless they are necessary,
- aim for sub 300 ms backend reads on common dashboard views where possible.
When to Use Launch Ready
Launch Ready fits when you already have a working Next.js plus Stripe product but need me to make it safe enough to ship without gambling on customer trust.
I would use this sprint if:
- your portal works locally but breaks under real traffic,
- you are worried about exposed secrets or bad deployment settings,
- you need Cloudflare and SSL set up correctly before launch,
- you want monitoring live before paid users hit production,
- you need a clean handoff after fixing security issues like this one.
What you should prepare:
- repo access,
- hosting provider access,
- domain registrar access,
- Cloudflare account,
- Stripe dashboard access,
- current environment variable list,
- screenshots of the leak if available,
- one sentence describing which users should see which data.
If your issue includes both leaking data and broken app structure around onboarding or billing flows, I would still start with Launch Ready because fixing deployment safety first prevents compounding mistakes during release week.
References
- https://roadmap.sh/api-security-best-practices
- https://roadmap.sh/cyber-security
- https://roadmap.sh/code-review-best-practices
- https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating
- https://docs.stripe.com/security/guide
---
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.