The 30-Minute Audit That Catches an AI-Built App Leaking Customer Data
Half an hour with your own app — logged out, in a private browser window — will tell you whether the booking form you shipped in March is serving your customer list to anyone who asks for it.
The news that prompted this
On 25 September 2026, TechCrunch reported that some Supabase customers are publicly exposing reams of people's data to the web. Read the headline again and note where the problem sits: on the customer's side of the line, in configuration. That's the bad news and the good news at once — nobody had to break in, and configuration is something you can check yourself today without hiring a security firm.
The same outlet reported the same day that unsecured OpenAI agents posted 53 user images on the internet without the lab's knowledge. We're not going to reconstruct what went wrong there from a headline, and it wasn't an SMB's app. It's simply a good prompt to go look at where your own AI workflows write files, which is minute 22 below.
Our own view on why this matters more this year than last: it's about who's writing the code. If you or a contractor built a booking form, a client portal, or an internal tool with an AI coding assistant, you got working software fast. Working is not the same as private. The assistant answered "make this form save leads to a table." It was never asked "who can read that table?"
What "leaking" actually looks like
It's rarely dramatic. Three boring mechanisms cover most of what we look for:
- The database is readable from the browser. Some app backends hand your web page a public API key on purpose — Supabase documents its publishable/anon key as safe to use in a browser precisely because it respects the row-level policies you've written. That's fine when those policies exist. When they don't, the key that loads your booking calendar can also load your whole customer table.
- Guessable URLs.
/invoice/1043works. So does/invoice/1044, which belongs to someone else. Broken object-level authorization is ranked first on the OWASP API Security Top 10 (2023 edition), and it's the first thing we go looking for in a hand-rolled portal. - Public files and share links. Uploaded IDs, signed contracts, before-and-after job photos, an automation's output folder. Don't assume the default here — check the actual setting on each bucket, folder, or share link, because it's set per object on most platforms.
None of these require an attacker. They require a browser.
Minutes 0–5: list what exists
Write down every app, form, portal, or automation built or changed in the last 12 months. For each one: what customer data it touches, who built it, where the data actually lives (Supabase, Firebase, Airtable, a Google Sheet, a hosted database), and who has the admin login. If you can't name where the data lives, that's the first thing to find out — everything below depends on it.
Minutes 5–10: log out and look
Open a private/incognito window. Visit your app as a stranger. Try to reach the pages that should need a login by typing their URLs directly — /dashboard, /admin, /bookings, /customers. Anything that loads is public. Not "probably fine because there's no link to it" — public. Treat "nobody knows the URL" as an assumption you can't test, not a control.
Minutes 10–17: the permissions check (the important one)
This is where the Supabase story lives. Supabase's documentation is explicit that the browser-side key is only safe when Row Level Security is enabled, because the key respects whatever RLS policies you've written. No policies, no protection.
So: log into the dashboard, open the table editor, and go table by table. As of September 2026, the Supabase dashboard flags tables with RLS disabled in the table list — but verify against what your own dashboard shows rather than taking this post's word for it. Enable RLS on every table holding customer data, then confirm each one has at least one policy that actually restricts rows to the right user. RLS enabled with a wide-open "allow all" policy is theater.
Other platforms, as of September 2026: on Firebase the equivalent is Security Rules, which you can check in the console and test with the Rules Playground / emulator tooling rather than assuming. On Airtable, check every shared view and base-share link and whether it's public. On Google Sheets, check for "anyone with the link."
Minutes 17–22: the URL-number test
Log in as a real test customer. Find any page with an ID in the address bar — an order, an invoice, a booking, a message thread. Change the number. If you can see another customer's record, you have the OWASP number-one problem, and the fix is a specific instruction to your developer: authorization must be checked on the server for every record request, not hidden by not linking to it.
Do the same with any file link: copy the URL of an uploaded document, paste it into the logged-out private window, and see if it opens.
Minutes 22–27: files, buckets, and anything an agent writes
Check the access setting on every storage bucket your app uses — on Supabase, storage access controls are separate from your table policies, so getting RLS right doesn't cover your files. Then check where any AI workflow saves its output: screenshots, generated documents, transcripts, call recordings. If an automation writes files to a location you've never opened in a browser, open it in a browser now, logged out.
Minutes 27–30: write it down
One page per app: where the data lives, who holds the admin account, which tables have policies, when you last ran this check, and who to call. Put a recurring 30-minute reminder in the calendar every quarter and after any significant change. One common way this breaks after launch: someone turns a policy off to debug something and doesn't turn it back on. That's why the re-check after changes matters as much as the first pass.
A worked example (illustrative — our made-up numbers, not a client's)
Every figure in this section is invented for illustration. A five-person HVAC company builds a booking form with an AI assistant in March. It works. By October, say the table holds 4,100 rows: name, service address, phone, gate codes, job notes.
Fixing it now: the owner's 30 minutes, plus an assumed two to four hours of developer time to add and test policies and move the ID lookups server-side. At an assumed-for-illustration freelance rate of $100–150 an hour, that's roughly $200–600. Call it a slow Tuesday.
Missing it: every state has a security-breach notification law, and the FTC publishes a data breach response guide for business that walks through notifying affected people. Using a made-up-for-illustration $1.50 per letter for printing, mail merge, and postage, 4,100 notifications is about $6,150 — before a lawyer reads a word of the notice, and before the week of phone calls from customers asking why their gate code was on the internet.
The asymmetry is the whole argument. Half an hour against an open-ended week.
If you find something open
This isn't legal advice, and what you owe whom varies by state and by what data was reachable. But in order: restrict access (turn on policies, make the bucket private), rotate any keys that were exposed, then preserve and check your logs for what was actually downloaded and when — that evidence is what a lawyer will use to work out whether you have a notification duty, and deleting the app deletes the evidence. Then call a lawyer if real customer data was reachable, using the FTC guide as your checklist. Fix the process last: the same contractor and the same prompt will produce the same gap next time unless "show me the permissions policies and a logged-out test" is part of what you accept.
Want a second pair of eyes on it?
If you'd rather have someone run this with you — or you got halfway through and found something you don't like — book a strategy call. Bring the list from minutes 0–5; that's the whole agenda.
Get weekly AI wins for small businesses
One useful email a week — practical AI wins, no fluff. Unsubscribe anytime.
