ACTIVATED HUMAN/ ai

My customers can see each other's data. How do I fix it?

Your customers can see each other's data because somewhere the app hands out a record without checking who is asking. The best-known form is a number in the address bar that can be changed, but it also happens through list pages and search that show every row, exports and emails sent to the wrong account, an admin view an ordinary user can reach, a cached page served to the next visitor, and an AI feature that answers from the whole table. A coding agent writes what it was asked for: 'show invoice 42'. Nobody asked it to add 'but only if invoice 42 belongs to this user', and in a demo with one account the gap never shows.

There are two fixes and you want both. The quick one is an ownership check in every place the code loads data. The lasting one is a rule in the database itself (row level security on Supabase, permission rules on Base44) so that a query without the check returns nothing. Then a feature the agent writes next month cannot reopen the hole.

If customers are on the app now, do it today: confirm the leak with two accounts, fix the worst table first, and decide what to tell the people affected. We can tell you what to check. What you must tell them is a question for a lawyer where you live.

In founders’ words

“I had Cursor write the endpoint that loads a user's saved views, looked fine in the diff so I approved it and moved on. Then I was testing something unrelated where i swapped the id in the url for a teammate's and it just handed me their stuff with no check at all that the row belonged to the person asking.”

r/cursor, October 2026 · source

The seven ways it happens

One example is never the whole problem. On your own app, with two test accounts, check each of these.

  • The number in the address. Open the first account's record as the second, then change the number at the end.
  • Lists and search. The detail page checks the owner; the list, search and 'recent activity' show everyone.
  • Exports and downloads. A CSV or PDF that reads the whole table and filters nothing.
  • Emails and notifications. A receipt or digest built from another account's rows. Trigger each as the second account.
  • Admin views. A staff screen an ordinary account can open by typing its address.
  • Caches. A page cached without the user in the key, so the next visitor gets the last visitor's data. Open the same page from both accounts within a minute.
  • AI features. A chat or summary that answers from the whole table. Ask it about a customer who is not you.

The eighth is direct database access: with the publishable key and no row rules, a table can be read with no page at all. The Lovable, Base44 and Bolt scanners check for that one.

Why the agent built it this way

The code was right for the case it was written for. When you asked for 'a page that shows a project', the agent fetched the project by its ID and rendered it. That is a complete, working answer to the request. The missing line, 'and this project belongs to the current user', is not a bug in what was written; it is an absence, and an absence does not show up in a diff or in a demo.

Each new feature repeats the pattern in its own way: the export queries the table, the email job queries the table, the AI feature is handed the table. So the number of unguarded places grows with the app, and by the time someone changes a number in the address bar there may be twenty. That is why the real fix lives in the database, where one rule covers every query, rather than in twenty separate places the agent has to remember.

Fix one: the ownership check in the code

Ask your coding agent to make one function the only way to load each kind of record. That function takes the current user and the record ID, and returns the record only if the user owns it or has been shared it. Every page, list, export, email job, AI feature and API call uses that function, and any cache key includes the user. Then it writes a test for each one: log in as user B, request user A's record, expect a refusal.

Put the rule in the agent's instructions file in the repository ('every data read goes through the owner-scoped loader; never query these tables directly') so it applies to the next feature too. This fix is fast and you can do it this afternoon. On its own it is not enough, because a future shortcut can bypass it.

Fix two: the rule in the database

On Supabase, which is what Lovable, Bolt and many Cursor and Claude Code apps use, the rule is called row level security. Supabase's docs put it bluntly: 'a table in an exposed schema without RLS is readable and writable by any role with a grant on it'. Once RLS is turned on, the API returns no rows at all until you add a policy, and the usual policy says the row's user_id must equal the logged-in user's ID.

Three things to watch. A policy that simply reads 'true' allows everyone; Bolt's Security audit flags these as 'RLS Policy Always True'. The service role key bypasses RLS entirely and must never be in the browser. And turning RLS on will break pages until the policies exist, so do it in a test copy first.

In Lovable you can read every policy under More, Cloud, Database, RLS policies. In Base44 the security scan lists tables with missing permission rules and offers recommended ones. On Replit the rule lives in your own server code, so fix one is the main fix and a test per table is what keeps it.

Keep it fixed

Keep a short table: each kind of record, who may read it, and the test that proves anyone else is refused, including through exports, emails and the AI feature. Once every row has a test, 'is it safe' is answered by pointing at the table rather than by remembering old diffs. Run the tests before every release, and run the seven checks by hand after any change to logins, sharing, caching or a new kind of record.

This is the shape of the work we do in a first look and a setup: read every place the app loads data, put the rule where it cannot be bypassed, and leave tests behind so it stays that way while the agent keeps building.

Where the ownership rule lives, by tool
Your app runs onWhere to lookWhat a good rule looks like
Supabase (Lovable, Bolt, Cursor and Claude Code apps)Supabase dashboard: Authentication, Policies. In Lovable: More, Cloud, Database, RLS policiesRLS on for every table; one policy per table that compares the row's user_id with the logged-in user; never a policy that is just 'true'
Base44Security scan, data permission issues; each table's permission rulesEvery table has rules; a record is readable by its owner and by people it was shared with, not by everyone logged in
Bolt databaseDatabase panel, SecurityNo 'RLS Policy Always True' warnings; service role key only in server functions
Replit, or your own server codeThe function that loads each recordOne owner-scoped loader per record type, used everywhere, with a test that user B is refused user A's record

Questions

Supabase says a table is publicly accessible. What does that mean?

It means one of your tables has row level security turned off, so anyone with your project's address (which is in your app's code for anyone to see) can read, change and delete every row in it. Supabase's Security Advisor reports this as an error, 'Table publicly accessible', and Supabase emails project owners when a table is created that way. It does not mean someone has taken your data; it means nothing stops them. Turn RLS on for that table in a test copy first, add a policy so your own app can still read its rows, then do the same on the live project, and check the Advisor again until the warning is gone.

Should I use RLS in Supabase?

Yes, on every table your app reaches through Supabase's API. Supabase's docs say a table in an exposed schema without RLS 'is readable and writable by any role with a grant on it', and tell you to enable RLS on every such table. RLS on with no policy blocks everything, so each table also needs a policy that matches its owner, usually the row's user_id against the logged-in user.

What does enabling automatic RLS mean in Supabase?

It is a setting that turns row level security on for every new table the moment it is created, so a table added by your coding agent or a migration cannot start out open. Supabase offers it as a one-click setup in the dashboard (it works through a database trigger), and tables created in the dashboard already get RLS by default. It covers new tables only: check your existing ones in the Security Advisor, and remember a new table still needs a policy before your app can read it.

Is this the same problem as the Lovable security advisory?

Yes, the same class. CVE-2025-48757, published in May 2025, described Lovable-built apps whose tables could be read or written by anyone because the row level security rules were missing. Lovable has since added scans for it, and the fix on this page is the fix for that advisory: RLS on, a real policy per table, and tests.

Will turning on row level security break my app?

Yes, at first. Once RLS is on, the API returns no rows until a policy exists, so every page that reads that table goes blank. Turn it on in a test copy of the database, add the policies, check every page, then do the same on the live one with a fresh backup taken first.

My app does not use Supabase. Does this still apply?

The leak is the same wherever the code loads records by ID without checking the owner. On Base44 the equivalent of RLS is the table permission rules. On Replit or a custom server, the fix is the owner-scoped loader and a test per record type; some databases also support row rules you can turn on later.

Do I have to tell my customers?

That depends on where you and they are, what was exposed, and for how long. Fix the leak first, write down what was reachable, and then ask a lawyer. We can help you establish the facts; we cannot tell you what the law requires.

How do I stop the agent doing it again?

Three things: the rule in the database, so a query without the check returns nothing; a line in the agent's instructions file naming the one loader it must use; and a test per record type that runs before every release. The agent will still write code that forgets the check. The tests catch it before customers do.

Working with us
  1. First look, $750. After a free call, we read your whole product and tell you what is finished, what is not, and what to do first.

  2. Setup, $3,000 fixed. We make it ready for real customers, in accounts you own.

  3. Partner, $2,500 a month. We review what your coding agent writes and keep the checks and tests current. Month to month.

Related questions