fixes / launch-ready

How I Would Fix database rules leaking customer data in a Lovable plus Supabase AI chatbot product Using Launch Ready.

The symptom is usually simple and scary: one customer logs in and sees another customer's chat history, profile fields, uploaded files, or internal notes....

How I Would Fix database rules leaking customer data in a Lovable plus Supabase AI chatbot product Using Launch Ready

The symptom is usually simple and scary: one customer logs in and sees another customer's chat history, profile fields, uploaded files, or internal notes. In an AI chatbot product, that can also mean prompt logs, embeddings, or tool outputs leaking across tenants.

The most likely root cause is weak Supabase Row Level Security, often combined with a Lovable-generated client that queries tables too broadly or uses the service role key in the wrong place. The first thing I would inspect is the live Supabase auth and policy setup, because if the policies are wrong, every other fix is just rearranging furniture.

Triage in the First Hour

1. Check whether the leak is reproducible with two test accounts.

  • Create User A and User B.
  • Log in as each one and compare chats, messages, files, and profile records.
  • Confirm whether the issue is cross-user data exposure or only admin views.

2. Open Supabase Dashboard > Authentication.

  • Verify users are actually authenticated.
  • Check whether anonymous access is allowed where it should not be.

3. Open Supabase Dashboard > Table Editor for every customer-facing table.

  • Inspect whether Row Level Security is enabled.
  • Check which tables store chats, messages, documents, embeddings, tool outputs, or audit logs.

4. Review all policies on those tables.

  • Look for `using (true)`, `with check (true)`, missing tenant filters, or policies that rely on client-supplied fields only.
  • Confirm policies reference `auth.uid()` or a trusted tenant identifier.

5. Inspect Lovable-generated frontend data calls.

  • Search for broad selects like `select("*")`.
  • Search for queries against admin-only tables from the browser.
  • Check whether any secret key is present in frontend code or environment variables exposed to the client.

6. Review Supabase logs and app error logs.

  • Look for requests returning more rows than expected.
  • Look for 401/403 errors that were bypassed by fallback logic.
  • Check if caching layers are serving user A data to user B.

7. Verify deployment environment variables.

  • Confirm the anon key is public as expected.
  • Confirm the service role key is never shipped to the browser.
  • Confirm separate dev and prod projects are not mixed up.

8. Freeze risky writes before changing policies.

  • If possible, pause chat creation or document uploads for 30 to 60 minutes.
  • This reduces damage while you patch access control.
-- Quick policy audit pattern
select schemaname, tablename, policyname
from pg_policies
where schemaname = 'public'
order by tablename, policyname;

Root Causes

1. RLS is off on one or more sensitive tables.

  • Confirmation: Table Editor shows RLS disabled on chats, messages, documents, embeddings, or user profiles.
  • Why it leaks data: Without RLS, any authenticated query can return rows if the client can reach the table.

2. Policies exist but are too broad.

  • Confirmation: Policies use `true`, `auth.role() = 'authenticated'`, or no tenant condition at all.
  • Why it leaks data: Every logged-in user gets access to every row.

3. The app uses a service role key in client-side code.

  • Confirmation: Search Lovable output and deployed bundles for `service_role` references or privileged API wrappers exposed in the browser.
  • Why it leaks data: Service role bypasses RLS completely.

4. Tenant ownership fields are missing or inconsistent.

  • Confirmation: Tables do not have `user_id`, `org_id`, or `workspace_id`, or inserts sometimes leave those fields null.
  • Why it leaks data: Policies cannot enforce ownership if ownership is not stored reliably.

5. Joined queries pull private child records without matching parent filters.

  • Confirmation: A chat list query joins messages or files but does not filter by parent conversation owner first.
  • Why it leaks data: One valid parent row can expose many unrelated child rows through a bad join path.

6. Caching or edge delivery serves stale personalized data across users.

  • Confirmation: CDN cache headers are set on authenticated responses, or frontend state persists between sessions after logout/login swaps.
  • Why it leaks data: The database may be correct while the delivery layer repeats someone else's response.

The Fix Plan

My rule here is simple: I would not "patch around" broken access control. I would fix authorization at the database layer first, then clean up frontend queries second.

1. Inventory every sensitive table.

  • Chats
  • Messages
  • Documents
  • Embeddings
  • Tool calls
  • User profiles
  • Billing records
  • Admin notes

2. Turn on RLS everywhere customer data lives.

  • Do this before any policy tuning so there is no accidental open window.
  • If a table must remain public, make that decision explicit and document it.

3. Add ownership columns where needed.

  • Use `user_id` for single-user products.
  • Use `org_id` or `workspace_id` for team products.
  • Backfill existing rows before enabling strict policies.

4. Write restrictive policies by default.

  • Allow select only when ownership matches authenticated identity.

```sql create policy "users read own chats" on public.chats for select to authenticated using (user_id = auth.uid());

5. Lock down inserts and updates too.
   - Select-only protection is not enough if a user can create rows under someone else's ID.
    ```sql
create policy "users insert own chats"
on public.chats
for insert
to authenticated
with check (user_id = auth.uid());

6. Remove privileged access from browser code.

  • Keep service role usage only in server functions, admin jobs, or secure backend routes.

Use anon key in client code only where RLS protects the query path.

7. Fix all frontend queries to request only what they need.

  • Replace broad selects with specific columns and scoped filters.

Example: fetch chat summaries from `chats`, then load messages only after confirming ownership.

8. Check AI chatbot storage paths separately from UI paths. Chatbot products often leak through:

  • prompt history tables

-, vector stores -, tool execution logs -, file upload metadata

Each of these needs its own access rule review.

9. Rotate secrets if there was any chance of exposure. If service keys were ever shipped to the client or committed to git: 1) rotate them, 2) redeploy, 3) invalidate old sessions if needed, 4) review audit logs for abuse.

10. Re-test with fresh accounts before reopening traffic. I would not rely on "looks fixed" from one browser session. I would verify with two separate users plus an incognito session and a direct API check.

Regression Tests Before Redeploy

I would treat this as an API security release with a hard gate before production goes live again.

  • Access control tests:

1. User A can read only User A records. 2. User B cannot read User A records by ID guessing or list endpoints alone. 3. Anonymous users cannot access private tables unless explicitly allowed.

  • Write protection tests:

1. User A cannot create rows owned by User B. 2. User A cannot update another user's chat title, message content, or file metadata.

  • AI-specific checks:

1. Prompt history does not expose other users' prompts in retrieval context. 2. Tool outputs do not include cross-tenant secrets in generated responses.

  • Session tests:

1. Logout clears cached private state on web and mobile browsers if applicable. 2. Login as another user does not show previous account data from local storage or React state.

  • Delivery tests:

1. No private API response is cached publicly by Cloudflare or browser cache headers incorrectly set to shared caching behavior。 2. Authenticated pages return proper no-store behavior where required.

Acceptance criteria I would use:

  • Zero cross-user record visibility across two test accounts and one admin account boundary check where relevant.
  • No sensitive table returns rows without matching ownership policy except approved admin paths with audited server-side access only。
  • Target test coverage for authorization rules at least 80 percent on critical flows。
  • Manual regression pass completed in under 90 minutes before redeploy。

Prevention

I would stop this coming back with three layers of guardrails: database rules, code review discipline, and monitoring.

  • Database guardrails:

* Enable RLS by default on every new table containing customer data。 * Require explicit approval before adding any policy that widens access。 * Keep a short policy matrix listing each table owner and allowed roles。

  • Code review guardrails:

* Review every query touching customer data for tenant filters。 * Reject any client-side use of service role keys。 * Flag `select("*")` on private tables unless there is a strong reason。

  • Monitoring guardrails:

* Alert on unexpected row counts per request。 * Track repeated forbidden requests as possible probing activity。 * Log auth subject IDs alongside sensitive reads so investigations are possible later。

  • UX guardrails:

* Show clear loading and empty states instead of reusing stale cached content after login changes。 * Add visible account context so users notice if they somehow land in the wrong workspace faster。

  • Performance guardrails:

* Index ownership columns like `user_id` and `org_id`. * Watch p95 query latency; keep common chat reads under about 200 ms at the database layer where practical।

When to Use Launch Ready

Launch Ready fits when you need this fixed fast without turning it into a long internal project you do not have time to manage yourself.

  • DNS and redirects,
  • Cloudflare setup,
  • SSL,
  • production deployment,
  • environment variables,
  • secrets handling,
  • uptime monitoring,
  • SPF/DKIM/DMARC,
  • handover checklist,
  • and basic launch safety checks around auth-sensitive flows।

What you should prepare before I start:

  • Supabase project access with admin permissions۔
  • Lovable project access۔
  • Domain registrar access۔
  • Cloudflare access if already connected।
  • A list of sensitive tables and current user roles।
  • Any known examples of leaked records।
  • Your desired launch domain and email sender details।

If you bring me a product that already has customer traffic but broken database rules, I will prioritize containment first and polish second। That avoids support tickets, trust damage, failed onboarding sessions, and expensive rework after launch۔

References

  • https://roadmap.sh/api-security-best-practices
  • https://roadmap.sh/code-review-best-practices
  • https://roadmap.sh/qa
  • https://supabase.com/docs/guides/database/postgres/row-level-security
  • https://supabase.com/docs/guides/auth/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.*

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.