The app that promised bank-level security, then served its whole user table to anyone
This is the first in a series where we take apart a real data exposure we found in the wild, with the app anonymized, so the pattern is clear and the next person avoids it. This one is the most common serious mistake in AI-built apps, and the app in question was actively advertising the exact security control it had switched off.
What was exposed
One database table, one row per registered user, returned everything to an anonymous request. No login, no password, no exploit. In the sample we looked at, which was a row count and a single masked record that we discarded, the fields included email, phone number, date of birth, and monthly income, filled in for several thousand real people.
That last field is the tell. This is not a mailing list. It is the exact profile an identity thief hunts for: contact, age, and income, pre-joined for thousands of people, and exposed to anyone who asks.
The part that stings: it advertised the control it had turned off
The product's own marketing page promised airtight, per-user data isolation and named bank-level security among its headline safeguards. The specific control that would have delivered that promise, row-level security (RLS), is precisely the feature that was not doing its job. The app shipped the promise of the control in its copy and the absence of it on its most sensitive table.
That gap, confident security language on top of an unconfigured backend, is the whole story of this class of leak.
The anatomy: how a browser ends up able to read your whole table
Apps built fast with an AI assistant on a backend-as-a-service like Supabase or Firebase talk to their database directly from the user's browser. To do that, the app ships a publishable key in its own frontend code. That key is meant to be public. Anyone can read it in developer tools.
The publishable key is safe only if one thing is true: the table has a rule saying who may read which rows. On Supabase that rule is row-level security (RLS) with a read policy; on Firebase it is a Firestore Security Rule. With it on and a sensible policy, the public key reads only the signed-in user's own row. With it off, or on with no policy, the same public key reads every row in the table.
There is no bug here and no attack. The database did exactly what it was configured to do. The configuration was simply wrong: one setting, left in its unsafe position, on a table full of real people.
How we found it, and what we did not do
We loaded the app the way any visitor does and read the publishable key from its own frontend. We asked the database's public endpoint for a row count and one masked sample row, the minimum needed to confirm the data was real and not demo seed data. Then we stopped.
We did not page through the table, did not download anyone's records, and kept nothing. The finding is the readability. You do not need to harvest the data to prove the door is open.
Why this is everywhere right now
The tools that let one person ship a real product over a weekend are genuinely good. But they make the database the first thing you build and the last thing you secure. On a new table the safe rule is off by default, and the safe-by-launch step, turning it on and writing a read policy per table, is the one an AI assistant will happily skip unless someone asks.
So the same misconfiguration shows up again and again. The app works, the demo looks great, the marketing says secure, and the user table is one public request away from anyone. And it is not only large platforms. In the same sweep we found a niche marketplace exposing uploaded identity documents the same way. One setting, real people's ID documents on the open internet.
The sixty-second fix
If you built on a backend-as-a-service, this is quick to close.
- Lock down every table that holds personal data. On Supabase, enable row-level security. On Firebase, write restrictive Firestore Security Rules.
- Add a policy so a signed-in user can read only their own row, with no anonymous read.
- Re-check: an anonymous request should now come back empty or forbidden, not full.
If an AI built it, ask it directly
Paste this to your assistant: review my database schema and migrations, identify any tables missing row-level security policies, and write a per-user read policy for each. It will do it in a minute. The gap was never skill. It is that nobody thought to ask.
What we did about this one
We emailed the team privately, in plain language, with the exact fix. No bounty demand, no threat, and nothing retained. We are not naming them, and we will not. The point of this series is not to shame a builder who missed a default setting. It is to show, concretely, how one default turns a working product into a data breach, so the next person turns it on before thousands of strangers' details are on the open internet.
One default setting, left as it ships, is the difference between a working app and a data breach. If your app talks to its database from the browser, turn on row-level security today and re-check with an anonymous request.
Is your database open right now?
Run the free database exposure check, or a full scan, no signup. Where it finds an open table, it proves it with the exact request and a screenshot.