There is a single setting in a modern database that decides whether your customers can read each other's records. It is off by default in the sense that matters: create a table, connect your app, and everything works beautifully — for you, for your test account, and for anyone who finds the address of your database.
The Lovable CVE made this concrete. Data was exposed across more than 170 applications built on the platform, and the pattern underneath was the same one that has been quietly repeating across the industry: row-level security never turned on.
What row-level security actually is
Most modern app databases can be reached directly from the browser. That is the point — it removes a whole server layer and it is genuinely faster to build with. The trade is that the database itself now has to decide who may see which rows, because there is no longer a server in the middle doing it for you.
Row-level security is that decision, written as a rule attached to the table: a user may read a row only if the row belongs to them. With it on, a request for someone else's data returns nothing. With it off, the database does exactly what it was asked and hands the rows over.
The failure is silent by design. Your own testing passes, because you are asking for your own data and you are entitled to it.
Why AI-generated apps get this wrong so consistently
Ask a model to build a working app and it will produce one. Access rules are not part of "working" — they are part of "safe", and nothing in the request signalled that safety was in scope. The generated code connects, reads, writes, and demos perfectly.
It compounds. The second feature is built on the first, the schema grows, and by the time anyone asks the security question there are forty tables rather than four. Retrofitting rules across forty tables means understanding every access path in the application, which is most of a rebuild.
This is the same shape as the broader finding that around 45% of AI-generated code introduces a flaw from the OWASP Top 10. It is not that the model writes bad code. It is that it writes code that answers the question it was asked.
The five-minute check
You do not need an engineer for the first pass. Ask whoever built it three questions and listen for the shape of the answer, not the detail:
Is row-level security enabled on every table that holds customer data? The right answer is "yes, and here is the list." "I think so" means no.
Which key does the browser use? There are typically two — a restricted one meant for the public, and an administrative one that bypasses every rule. If the administrative key is anywhere in the front end, the rules are decoration.
What happens if I request record number one? Someone should be able to demonstrate this, live, from a second account. If the record comes back, you have your answer and you have it before a stranger does.
What good looks like
Rules on every table holding customer data, written the day the table is created rather than the week before launch. The administrative key server-side only. A test in the suite that logs in as one customer, asks for another's data, and fails the build if it arrives.
That last one is the part almost nobody has, and it is the cheapest of the three. It converts a security property from something you believe into something the machine checks on every commit.
The honest summary
This is not an exotic attack. There is no clever exploit chain, no zero-day, no attacker of unusual skill. It is a setting, and the cost of leaving it off is every record you hold.
If you are not sure which side of that line your system sits on, that is a normal position to be in and a bad one to stay in. Finding out takes an afternoon; finding out from a customer takes considerably longer. A Reality Check is one week and a fixed fee, and this is one of the first things it looks at.
This is the part we do — the crossing from a demo to a system that survives production.