Skip to content
BuildThenTestedSee packages

Methods

Two test accounts: the cheapest security test you can run

Create two users, try to read and change each other's records, open the admin URL, and call a paid endpoint from the free account. Half an hour, no code.

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

  1. 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.
  2. In each account, create one record of every kind the app has: a project, an order, a note, a profile change, an uploaded file.
  3. 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

Patterns in this post

Questions

how do i test if users can see each others data in my app

Create two accounts, add a record in each, copy the address of account A's record and open it in a private window signed in as B. Then find the request that loads it in developer tools and try it with B's session and with ids near your own. Any of A's data appearing for B is a critical finding.

what is an idor test

IDOR stands for insecure direct object reference: the app looks up a record by the id you send and returns it without checking that you own it. The test is to change the id in a request while signed in as a different user and see whether another user's record comes back.

how to check if a paid plan is enforced on the server

From a free account, copy the request a paid feature makes and run it directly, and look in the browser's local storage for a plan value you can change. If the feature works or appears, the check runs in the browser and needs to move to the server or the database rules.