The most common serious flaws in apps built with AI app builders are not exotic. They are cases where the app trusts the browser to decide who may see what. You can test for the whole family with two accounts, a private browser window and half an hour. This post is the method we use first on every engagement, written so you can run it yourself.
Why two accounts
A single account cannot tell you whether the app keeps users apart. Two can. Account A owns a record; account B should not be able to see or change it. Every step below is a variation on that one question, asked of a different part of the app.
One education app built on Lovable had a database function that denied callers whose role was "authenticated", so anonymous callers passed and 18,697 user records were readable without login (Taimur Khan, 2026). The flaw was in the app's own function logic, not in Lovable's platform. The researcher found it by asking the same question this post asks: what does the app return when the caller is not who it expects. A two-account test would have shown it.
Set up
- Create account A and account B with different email addresses. Use a normal window for A and a private window for B, so the two sessions do not share cookies.
- In each account, create one record of every kind the app has: a project, an order, a note, a profile change, an uploaded file.
- Open developer tools in A's window: right-click, choose Inspect, then the Network tab. Leave it open. It records every request the page makes.
Test one: read each other's records
In A, open one of A's records and copy the address from the browser bar. Paste it into B's private window. If the page shows A's record, stop and write it down; that is a critical finding. Then look at the Network tab in A for the request that loaded the record, usually an address containing an id. Right-click it, choose Copy as cURL, and run it from B's session, or simply change the id in the address bar to one you saw in B's own requests and reload.
Try ids one higher and one lower than yours. Sequential ids make this easy; random ids make it harder but do not fix the underlying check. The pattern is Record ids accepted without an ownership check, which security testers call IDOR, an insecure direct object reference.
Test two: change each other's records
Repeat test one with the edit and delete actions. In A, edit a record and find the request in the Network tab. Copy it as cURL, swap in an id belonging to B, and run it. Then check B's account to see whether the record changed. A bad result is any change landing in the other account. This is the same pattern with a worse outcome, since a read leaks data and a write corrupts it.
Test three: the admin URL
Guess the admin address. Try /admin, /dashboard/admin, /api/admin and /api/users as B, then in a third window with no login at all. Also open the bundle: in developer tools, Sources tab, search all files for admin and try each route you find. A page that renders, or an API that returns data, is a finding even if the navigation never shows the link. The pattern is Admin routes without any login check.
Test four: the paid feature from the free account
If the app has plans, keep B on the free plan and find the request a paid feature makes in A. Copy it as cURL and run it from B's session. Then look for where the plan is decided. In B's window, search Sources for words like isPro, plan or subscription and look for a value stored in the browser, such as in local storage under the Application tab. Change it to the paid value and reload. If the paid feature appears, the check runs only in the browser. The patterns are Authorization enforced only in the browser and Price or plan taken from the client. For the price itself, find the checkout request and look for an amount in the body; a server should choose the price from its own Price ID, never from a number the browser sends.
What a bad result looks like
Write each one down with the address, which account you were in and what came back. You are looking for:
- A's record, or any row that is not B's, appearing in B's window or in a response to B's request.
- An edit or delete from B changing A's data.
- An admin page or admin API answering for B or for a stranger.
- A paid endpoint answering for the free account, or a paid feature appearing after changing a stored value.
Each of these is a server-side fix: check the caller's identity and ownership in the database rule or the route handler, not in the page. Hand the pattern page to your developer or paste its fix direction into your builder's chat, then rerun the same test. Passing means B failed to get A's data, not that the code looks right.
What to do this week
- Run tests one and two on every record type you have: Record ids accepted without an ownership check.
- Run tests three and four: Admin routes without any login check and Price or plan taken from the client.
- Answer the self-check to see which other patterns apply to your stack: Self-check.