$ cat blog/data-leaks-in-ai-built-apps.md

How Do Data Leaks Happen in Apps Built With AI?

A data leak rarely looks dramatic. It is usually a quiet door left open. Here is how the common ones happen in AI-built apps, and how to spot them before a stranger does.

saasreview·June 14, 2026·12 min read

Data leaks in AI-built apps almost never look like a break-in. They look like a door someone forgot to lock: a file bucket left public, an API that hands back data without checking who is asking, or a record ID in the URL you can change to read someone else's account. None throw an error. They just quietly work for the wrong person.

What counts as a data leak, in plain terms?

A data leak is when information meant for one person, or for nobody outside your team, can be reached by someone who should not have it. It does not require malice or skill. If a logged-out visitor, or a logged-in user poking at the URL bar, can pull up data that is not theirs, that is a leak. Here are the everyday shapes it takes.

  • The public bucket. Uploaded files (profile photos, invoices, exports) sit in cloud storage that is set to public by default. Anyone with the link, or who can guess the folder pattern, can download them. No login, no trace.
  • The over-sharing API. A page only shows a user their own name and email, but the API call behind it quietly returns the whole user record: password reset tokens, internal flags, other people's data in the same response. The screen hides it. The network tab does not.
  • The editable ID. Your URL reads /invoice/1042. You change it to /invoice/1041 and someone else's invoice loads. The app trusted the number in the address bar instead of checking whether that invoice belongs to you.

That last one has a name, and it is worth knowing because it is one of the most common flaws in apps built fast. We will get to it in a second.

Why do AI-built apps end up with open doors no one intended?

AI-built apps leak because the tool is optimizing for code that runs, not for who is allowed to see what. When you ask Cursor, Claude, Lovable, or v0 to 'build a page that shows the user's invoices', it writes code that fetches invoices and puts them on the screen. It works. You see your invoices. What it usually skips is the unglamorous line that says 'and only if these invoices belong to the person currently logged in'.

That missing check is invisible precisely because everything looks correct. This is the blind spot at the heart of most of these bugs: you tested it as yourself, with your own data, and your data showed up. The app passed. But you never tested it as a stranger, because to you the stranger does not exist yet. The same pattern shows up across why real users break apps that worked for you: the product behaves for its maker and breaks for everyone else.

//It is not that AI writes 'bad' code

The code is often clean and readable. The problem is what it leaves out. Permission checks are a judgment call about your business rules, and a model guessing at a prompt has no way to know that invoice 1041 must never be visible to the owner of invoice 1042. It builds the happy path and moves on.

What is an insecure direct object reference, without the jargon?

An insecure direct object reference (often shortened to IDOR) is the editable-ID problem. It happens when your app uses a simple, guessable identifier (like 1041, 1042) to fetch a record, and then trusts that identifier instead of checking ownership. Change the number, get someone else's data. That is the whole bug. It is one of the most common and most damaging flaws in apps built quickly, because it requires zero technical skill to exploit. Anyone who can edit a URL can do it.

Here is the difference in two lines. The leaky version trusts the ID:

$js
// Leaky: fetches whatever ID is in the URL, no questions asked
const invoice = await db.invoice.find(req.params.id)
return invoice

// Safer: only returns it if it belongs to the logged-in user
const invoice = await db.invoice.findOne({
  id: req.params.id,
  ownerId: req.user.id,   // the check the AI usually forgets
})
if (!invoice) return notFound()
return invoice

The fix is one clause: also match on the owner. If that line is missing across your app, every record with a guessable ID is reachable by anyone logged in. Sequential numbers make it trivial. Even random-looking IDs are not real protection, because they often leak through other pages, shared links, or old emails.

How can I check if my data is reachable without logging in?

You can check most of this yourself in about ten minutes, with no special tools, by acting like a stranger instead of like the owner. The goal is to see what your app reveals to someone who has not logged in, and to someone logged in who pokes where they should not. Work through this checklist.

  1. 1.Open your app in a private/incognito window so you are fully logged out. Try to load pages that should be private: a dashboard, an account page, a file URL. If anything loads, that is a leak.
  2. 2.Copy a file URL (a profile image, an uploaded document) and paste it into that logged-out window. If it downloads, your bucket is public. Now try changing one character in the filename or folder and see if a different file appears.
  3. 3.Open your browser's network tab (right-click, Inspect, then Network), reload a page, and read what your API actually returns. Look for fields you never display: other users' emails, tokens, internal IDs, full records. The screen hides them. The response does not.
  4. 4.Change an ID in the URL. If you see /order/200, try /order/199. Log in as a second test account and try to load the first account's records by their IDs. If someone else's data appears, you have an IDOR.
  5. 5.Try the API directly without your login. If you can guess an endpoint like /api/users or /api/orders, open it logged out. A real endpoint should refuse you. A leaky one returns the data.

+Make two test accounts

The fastest way to find data leaks is to create two accounts, log into one, and try to reach the other one's data by editing IDs and URLs. You are simulating exactly what a curious or malicious user does. If account A can ever see account B's anything, write it down.

What should I do first if I find data is exposed?

If you find exposed data, close the door before you do anything else, then figure out who walked through it. The order matters. Every hour the door stays open is more exposure happening right now, not in some future worst case. Move in this sequence.

  1. 1.Stop the bleeding. Set the public bucket to private. Add the missing ownership check to the leaky endpoint. If you cannot fix it cleanly in minutes, take the feature offline temporarily. A missing feature is recoverable. Leaked customer data is not.
  2. 2.Rotate anything that leaked. If API keys, tokens, or secrets were reachable, treat them as burned and replace them. See are my API keys or secrets exposed? for how to do this without breaking your app.
  3. 3.Figure out the scope. What exactly was reachable, and for how long? Check your logs if you have them. Be honest with yourself about whether real user data was involved.
  4. 4.Tell affected people if real personal data was exposed. This feels awful and it is the right move. A calm, plain note that says what happened and what you did builds more trust than silence ever could. Honesty and disclosure build trust, even, especially, when the news is bad.
  5. 5.Fix the pattern, not just the instance. If one endpoint forgot the ownership check, others probably did too. Search your code for every place that fetches a record by ID and confirm each one checks ownership.

How do I reduce the chance of a leak going forward?

You reduce future leaks by making 'who is allowed to see this' a deliberate question on every screen and every endpoint, instead of a thing you hope the AI handled. You do not need to become a security engineer. You need a few habits and one default mindset: assume nothing is private until you have proven it is.

  • Make buckets private by default, and serve files through short-lived signed links instead of permanent public URLs.
  • Add the ownership check everywhere a record is fetched by ID. Make it boring and consistent so it is hard to forget.
  • Return only the fields the screen needs. If a page shows a name, the API should send a name, not the entire user row. Trim your responses.
  • Treat the URL and any client input as untrusted. The browser can send anything. The server decides what is allowed, every time, on the server.
  • Re-run the logged-out and two-account tests after any change that touches data. New features quietly reopen old doors.

None of this is exotic. It is the unglamorous layer AI tools skip because it does not show up on the screen. Building it back in is most of the work of making an AI-built app actually safe. For the bigger picture, the hub piece is my AI-built app safe? walks through the whole plain-English check.

When is it worth having someone probe from the outside?

It is worth an outside probe when you are about to put your app in front of real people or charge money, because that is the moment a quiet leak stops being a private worry and becomes a customer you lose, or worse. You can and should do the self-checks above. But you built the thing, so you carry the curse of knowledge: you keep testing the paths you expect, with the data you know, in the order you designed. A stranger does not.

That is what the Bughunt is for. It is an honest, automated probe from the outside that looks at what a nosy stranger could reach, then hands you a private, plain-English list of what it found. It is a passive automated check, not a human red-team breaking in, and we say that plainly so you know exactly what you are getting. The result never gets published. It is for your eyes, so you can close the doors before anyone else finds them.

Want to see what a curious stranger could reach in your app, before they do? Run a Bughunt and get a private, plain-English list of what is exposed.

Run a Bughunt on my app
// faq

Frequently asked questions

Can people see my database?

Usually not the database directly, but they can often see what it contains through a leaky API or a public file bucket. If an endpoint returns data without checking who is asking, or files sit in public storage, the contents are reachable even though the database itself is locked. Test by opening your API logged out and pasting file URLs into an incognito window.

Is my API returning too much data?

Probably, if you have not checked. A common pattern is a page that shows one or two fields while the API behind it sends the entire record: other users' emails, tokens, internal flags. The screen hides it; the browser network tab shows the raw response. Open it, read what comes back, and trim the API to return only what the screen needs.

What does it mean when changing the ID in the URL shows another user's data?

It means your app trusts the ID in the address bar instead of checking ownership. This is called an insecure direct object reference, or IDOR. The fix is to also confirm the record belongs to the logged-in user before returning it. It is one of the most common flaws in apps built fast, and it needs no technical skill to exploit.

How do I test for exposed data without being technical?

Open your app in an incognito window so you are logged out, then try to load private pages and file URLs. Make two test accounts, log into one, and try to reach the other's data by editing IDs in the URL. If anything that is not yours shows up, you have found a leak. No code or tools required, just curiosity and a browser.

How do I prevent a data leak in an AI-built app?

Make 'who is allowed to see this' a deliberate check on every screen and endpoint. Keep file buckets private, add an ownership check wherever a record is fetched by ID, return only the fields a page needs, and treat anything from the browser as untrusted. Re-run logged-out and two-account tests after every change that touches data.

Find the open door before a stranger does

A Bughunt is an honest, automated probe from the outside. It shows you what a curious stranger could reach and hands you a private, plain-English list. Nothing gets published.

Run a Bughunt on my app
$ ls related/

Keep reading

We put every SaaS through the same honest scorecard, then publish the result.

Published on saasreview.ai · last updated June 14, 2026