How I Would Fix database rules leaking customer data in a Cursor-built Next.js internal admin app Using Launch Ready.
The symptom is usually simple: someone opens an internal admin screen and sees records they should not see, or support reports that customer data from one...
How I Would Fix database rules leaking customer data in a Cursor-built Next.js internal admin app Using Launch Ready
The symptom is usually simple: someone opens an internal admin screen and sees records they should not see, or support reports that customer data from one account appears in another account's view. 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 where the app actually decides who can read what: database rules, server actions, API routes, and any admin-only queries. If the frontend hides data but the backend still returns it, the leak is already happening.
Triage in the First Hour
1. Check the exact exposure path.
- Is the leak happening in a page load, API route, server action, or direct database query?
- Open browser dev tools and inspect network responses for customer fields that should never be returned.
2. Review recent deploys and branches.
- Look at the last 3 commits, especially anything generated or edited in Cursor.
- Confirm whether a schema change, rule change, or auth refactor shipped with the leak.
3. Inspect logs for unauthorized access patterns.
- Check application logs for requests to admin endpoints with unexpected user IDs, tenant IDs, or missing auth context.
- Look for repeated 200 responses where you expected 403 or 404.
4. Verify database rules and row-level security.
- If you use Postgres RLS, Firebase rules, Supabase policies, or similar controls, inspect them first.
- Confirm whether the policy is too broad, missing tenant filters, or bypassed by service-role credentials.
5. Check environment variables and secrets.
- Confirm no production service key is exposed in client-side code.
- Review `.env`, Vercel env vars, and any shared preview deployments.
6. Audit admin access paths.
- Verify who can access `/admin`, `/internal`, and any hidden dashboards.
- Confirm role checks happen on the server, not only in React components.
7. Freeze risky changes.
- Pause new deploys until you know whether this is a data exposure incident or only a broken authorization rule.
- If real customer data was exposed, treat it as a security issue and notify stakeholders immediately.
8. Capture evidence before editing anything.
- Save screenshots of the bad response payloads.
- Export the current policy definitions and commit hashes so you can compare before and after.
Root Causes
| Likely cause | What it looks like | How I confirm it | | --- | --- | --- | | Missing row-level security | Any authenticated user can read all rows | Query as two different users and compare returned records | | Overbroad database policy | Policy uses `true`, weak wildcard matching, or bad tenant logic | Read the policy text and test with known tenant IDs | | Service role used in client path | Browser requests return full data because privileged key is exposed server-side or bundled incorrectly | Search for service keys in client code and build output | | Authorization only in UI | Buttons are hidden but API still returns sensitive rows | Call the endpoint directly with curl or Postman | | Broken tenant filter in query | Queries omit `where tenant_id = ...` or join through unsafe relations | Inspect server actions and SQL generated by ORM | | Cursor-generated shortcut bypassed review | AI wrote a fast path that skipped auth checks or reused an old helper incorrectly | Diff recent AI-assisted commits against known-safe patterns |
A common pattern in internal apps is false confidence from "it's behind login." That is not access control. If the backend does not enforce tenant boundaries, one bad query can expose every customer record.
Here is a quick diagnostic command I would run on a Next.js repo to find risky paths:
grep -RIn "service_role\|admin\|tenant_id\|auth()\|getServerSession\|createClient" app src lib . \ | head -n 100
This does not prove safety, but it quickly shows where privileged access may be happening and where to focus review.
The Fix Plan
1. Lock down data access at the database layer first.
- Add or correct row-level security so reads are scoped by tenant or account ID.
- Do not rely on frontend filtering as your main control.
2. Separate public session context from privileged server access.
- Use normal user auth for user-scoped reads.
- Reserve service-role credentials for trusted server-only tasks like migrations or back-office jobs.
3. Move sensitive reads behind server-side authorization checks.
- In Next.js route handlers or server actions, verify session identity before querying.
- Require an explicit tenant match before returning any customer row.
4. Remove broad queries from shared helpers.
- If one helper fetches "all customers," split it into:
- `getCurrentTenantCustomers()`
- `getCustomerByIdForAdmin()`
- `getCustomerByIdForSupport()`
- This reduces accidental reuse across screens.
5. Patch every endpoint that returns customer data.
- Internal apps often have multiple entry points: list views, exports, search endpoints, CSV downloads, webhooks, and background jobs.
- I would audit all of them because one unprotected export endpoint can undo every other fix.
6. Add defensive defaults to queries.
- Require `tenant_id` as an input parameter on server-side fetch functions.
- Fail closed if auth context is missing instead of returning "everything."
7. Rotate secrets if there was any chance of exposure.
- Rotate database credentials if service keys were logged or bundled incorrectly.
- Rotate any API keys that could have been read through leaked config files.
8. Clean up cached responses and previews.
- Purge CDN caches if sensitive JSON was cached publicly by mistake.
- Delete stale preview deployments that may still expose old behavior.
9. Tighten deployment settings in Launch Ready scope if needed.
- I would make sure domain routing, SSL, redirects, subdomains, Cloudflare protections, SPF/DKIM/DMARC email settings, environment variables, secrets handling, uptime monitoring, and handover notes are all correct before re-enabling traffic.
The business rule here is simple: if one internal admin screen can leak customers across tenants once, it can do it again under load or after the next edit unless I move enforcement into the backend and database.
Regression Tests Before Redeploy
I would not ship this fix without proving three things: unauthorized users cannot read data, authorized users still can read their own scope, and nothing else broke.
Acceptance criteria:
- A user from Tenant A cannot read Tenant B records through UI pages or direct API calls.
- A non-admin user gets 403 or empty results where appropriate; never cross-tenant data.
- Admin-only screens still work for approved roles only.
- Exports return only permitted rows.
- No secret appears in client bundles or browser network payloads beyond what is required.
QA checks:
1. Test with at least 3 roles:
- normal user
- support agent
- super admin
2. Test at least 2 tenants:
- Tenant A
- Tenant B
3. Verify negative cases:
- direct URL entry
- forged request body
- tampered query string
- stale session after role downgrade
4. Check response payloads:
- no hidden fields like notes, billing details, tokens, internal flags unless explicitly allowed
5. Run smoke tests on critical flows:
- login
- dashboard load
- customer search
- export CSV
- edit record
- delete record if present
6. Review logs after test runs: - confirm denied attempts are logged without leaking PII into logs themselves
I would also want at least 80 percent coverage on authorization-critical helpers and route handlers before calling this safe enough to deploy again. For a production internal app handling customer records, that is a minimum bar, not a luxury.
Prevention
1. Make authorization part of code review.
- Every PR touching data access should answer: who can read this row?
- If that answer lives only in React state,
I reject the change.
2. Keep database rules versioned and reviewed like code.
- Store policies alongside migrations where possible.
- Require peer review for any rule that touches customer tables.
3. Add automated security tests
- Write tests that prove cross-tenant reads fail by default.
- Include cases for exports,
search, pagination, and edge IDs, because leaks often hide there.
4. Log safely
- Log request IDs,
actor IDs, role names, and denied actions, but never raw customer payloads unless absolutely necessary and redacted first.
5. Use least privilege everywhere
- Separate reader,
writer, and admin credentials.
- Do not let preview environments point at production databases unless there is a very strong reason.
6. Monitor for abnormal access
- Alert on sudden spikes in admin queries,
large exports, repeated permission failures, and unusual cross-account lookups.
- For an internal app,
even 10 failed unauthorized reads in 5 minutes should trigger review.
7. Review AI-generated code more aggressively
- Cursor can speed up delivery,
but it also makes it easy to copy patterns that look right but skip enforcement details.
- I treat AI-assisted auth code as high risk until proven otherwise with tests.
8. Improve UX around permissions
- Show clear "not authorized" states instead of broken blank pages where users keep retrying unsupported actions.
- This cuts support load because people understand whether they lack access or whether something is broken.
When to Use Launch Ready
I use it when the problem spans deployment hygiene plus production readiness: domain setup, email deliverability, Cloudflare protection, SSL, secrets handling, monitoring, redirects, subdomains, and final handover.
For this kind of leak fix, I would ask you to prepare:
1. Repository access to the Next.js app plus any backend repo if separate 2 . Database access with policy edit rights 3 . Hosting access such as Vercel, Cloudflare, Render , Railway , or similar 4 . A list of roles , tenants , and example records that should be visible versus hidden 5 . Recent incident notes , screenshots , and any support tickets about leaked data
If your app has active customers , I would pair Launch Ready with an immediate security audit sprint so we fix both exposure risk and deployment risk together rather than patching one while leaving another open .
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/authentication
- https://supabase.com/docs/guides/database/postgres/row-level-security
---
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.