How I Would Fix database rules leaking customer data in a Cursor-built Next.js AI chatbot product Using Launch Ready.
The symptom is usually blunt: one user can see another users chat history, uploaded files, or account details. In an AI chatbot product, that turns into a...
How I Would Fix database rules leaking customer data in a Cursor-built Next.js AI chatbot product Using Launch Ready
The symptom is usually blunt: one user can see another users chat history, uploaded files, or account details. In an AI chatbot product, that turns into a trust problem fast, because the leak can happen through the UI, the API, or the database itself.
The most likely root cause is weak row-level access control. In Cursor-built Next.js apps, I often find queries that trust client-provided IDs, server routes that skip auth checks, or database rules that are too broad because the first version was built to ship quickly.
The first thing I would inspect is the path from browser to database. I want to know exactly which request returns customer data, which user identity is attached to it, and whether the database enforces ownership even if the app code fails.
Triage in the First Hour
1. Check the affected screens.
- Open chat history, profile, file library, billing, and admin views.
- Confirm whether the leak is visible in the UI or only through API responses.
2. Inspect recent deploys.
- Look at Vercel, Netlify, Cloud Run, or your host release history.
- Note any deployment in the last 24 hours before the leak started.
3. Review server logs and edge logs.
- Search for requests returning other users records.
- Look for missing auth claims, 200 responses on private endpoints, and repeated access to list endpoints.
4. Check database rule files and migrations.
- Review RLS policies, security rules, grants, and schema changes.
- Confirm whether rules were widened during a quick fix.
5. Inspect API routes in Next.js.
- Audit `app/api/*`, route handlers, server actions, and any direct DB calls.
- Verify every request derives identity from session context, not from a request body field.
6. Review auth provider settings.
- Check Clerk, Auth.js, Supabase Auth, Firebase Auth, or custom JWT config.
- Confirm session tokens are valid and map to one user only.
7. Compare affected accounts.
- Test with two separate users in staging if possible.
- Confirm whether tenant boundaries are broken by org ID or user ID mismatch.
8. Freeze risky changes.
- Pause new deploys until you know where data access is leaking.
- If needed, temporarily disable private endpoints or hide affected features behind maintenance messaging.
A simple diagnosis flow looks like this:
Root Causes
1. Missing row-level security or equivalent ownership rules
- Confirmation: query the table directly and see that rows are readable without strict tenant filters.
- Common sign: policies exist for writes but not reads.
2. Client-controlled identifiers in queries
- Confirmation: inspect requests using `userId`, `orgId`, `chatId`, or `threadId` from the browser payload.
- Common sign: changing an ID in DevTools returns another users data.
3. Server route skips auth verification
- Confirmation: route handler returns data even when session is absent or invalid.
- Common sign: code uses `req.body.userId` instead of `session.user.id`.
4. Overbroad service role usage
- Confirmation: search for admin keys used inside normal app flows.
- Common sign: a privileged key is exposed in serverless functions that should have been scoped down.
5. Bad multi-tenant joins
- Confirmation: inspect joins between chats, messages, files, and organizations.
- Common sign: one table filters by `user_id` while another filters by `org_id`, creating cross-account leaks.
6. Migration or seed script opened access too widely
- Confirmation: review recent schema changes for permissive grants or copied policies from a dev environment.
- Common sign: staging settings were promoted into production unchanged.
The Fix Plan
I would fix this in layers so we stop the leak first and then make sure it cannot come back through another path.
1. Contain immediately.
- Disable any endpoint that returns private customer records if you cannot confirm its authorization logic.
- Rotate exposed secrets if service keys may have been logged or shipped incorrectly.
- If there is active exposure of customer content, treat it as an incident and notify only after you know scope.
2. Lock down database access first.
- Turn on row-level security for every customer-owned table.
- Write policies that require ownership by authenticated user ID or org ID.
- Deny by default and add explicit allow rules only where needed.
3. Remove trust from client input.
- Never accept ownership fields from the browser as proof of access.
- Derive identity from verified session claims on the server only.
- Replace queries like "get all chats where userId equals request body" with session-bound queries.
4. Audit every Next.js route handler and server action.
- Add auth checks at the top of each private route.
- Validate parameters with a schema validator before touching the database.
- Return generic errors instead of exposing record existence across tenants.
5. Separate public and private data paths.
- Public marketing content should live on public endpoints only.
- Customer content should require authenticated server-side access with strict policy enforcement.
- If you use AI tools to summarize chats or files, pass only scoped records into those jobs.
6. Tighten secrets and environment variables.
- Move all privileged keys out of client bundles immediately if any are present there.
- Keep production secrets separate from staging secrets with different access controls.
- Rotate keys after fixing code paths so old credentials cannot keep bypassing policy mistakes.
7. Add defensive logging without leaking content.
- Log user ID, org ID, endpoint name, status code, and policy failures.
- Do not log raw prompts, file contents, tokens, email addresses, or full chat transcripts unless absolutely necessary and redacted.
8. Patch then verify in staging before prod redeploy.
- Reproduce with two test accounts against staging data only.
- Confirm both direct page loads and API calls fail correctly when unauthorized.
If I were doing this as Launch Ready work, I would keep it boring on purpose: smallest safe change first, then broader cleanup once access control is proven stable.
Regression Tests Before Redeploy
I would not ship this without tests that prove customer isolation under real conditions.
- Authentication tests
- Unauthenticated requests to private endpoints return 401 or redirect to login as intended.
- Expired sessions do not return data.
- Authorization tests
- User A cannot read User B chats by changing IDs in URL params or request bodies.
- Org member cannot read another orgs records even if they guess identifiers.
- Database policy tests
- Direct reads fail unless policy conditions match exactly.
``` p95 target for private API responses: under 300 ms ```
- Integration tests
- Chat list loads only owned conversations after login refreshes and role changes.
- File attachments remain scoped to one account across reloads and pagination.
- Negative tests
- Tampered JWTs fail closed.
- Missing org membership blocks access cleanly with no partial data returned.
- QA acceptance criteria
| Check | Pass condition | | --- | --- | | Cross-account read attempt | Blocked every time | | Private endpoint without session | Denied | | Policy coverage | All customer-owned tables protected | | Error handling | No sensitive details leaked | | Logging | Enough to debug without exposing content |
- Exploratory testing
- Test old links from bookmarks after logout and account switchovers.
- Test mobile browser behavior where cached pages may still show stale content briefly.
Before redeploying I would also run a quick security review on dependency updates if any auth libraries changed during the fix. A rushed patch that upgrades packages without testing can create a second outage on top of the leak.
Prevention
1. Make authorization part of code review every time. The reviewer should ask one question first: "Can this endpoint return someone elses data?" If that question is not answered clearly in code review notes, I would not approve it.
2. Use deny-by-default database policies everywhere possible. Tables holding chats, messages, uploads, billing info, embeddings metadata, and audit events should all have explicit read rules tied to ownership.
3. Add automated security tests to CI. I would add at least one cross-account access test per sensitive resource type before merge. Aim for coverage on critical auth paths above 80 percent.
4. Keep secrets out of client-side code forever. Any key that can read customer data must stay server-only with least privilege. If a secret ever reaches the browser bundle once then assume it needs rotation.
5. Monitor for unusual access patterns. Set alerts for repeated forbidden reads before successful reads from new IPs or impossible account switching behavior within minutes.
6. Improve UX around permission failures. Show a clear "You do not have access" state instead of blank screens or confusing retries. Bad error UX makes founders think something is broken when it may actually be blocked correctly.
7. Limit third-party scripts on authenticated pages. Extra scripts increase attack surface and can complicate incident response if they touch session state or DOM content unexpectedly.
8. Keep performance predictable while securing access paths: secure queries should still stay fast enough for chat UX; I usually target p95 under 300 ms for list views so users do not feel like auth checks made the product sluggish.
When to Use Launch Ready
Use Launch Ready when you need me to stop a production-risk problem fast without turning your product into a rebuild project.
What you should prepare:
- Access to your repo and hosting platform
- Database admin or limited migration access
- Auth provider dashboard access
- Cloudflare DNS access
- Production environment variable list
- A short description of what data leaked and when
What I will usually deliver:
- A clean production deployment path
- Locked-down env var handling
- Verified domain and email setup
- Monitoring so you know if access breaks again
- A handover checklist your team can use without guessing
If your issue is leaking customer data through bad rules or broken authorization logic then Launch Ready is a good first sprint because it removes launch blockers while we stabilize the security foundation underneath them.
References
- https://roadmap.sh/api-security-best-practices
- https://roadmap.sh/cyber-security
- https://roadmap.sh/code-review-best-practices
- https://supabase.com/docs/guides/database/postgres/row-level-security
- https://nextjs.org/docs/app/building-your-application/routing/route-handlers
---
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.