Cursor wrote your API — but who checked the auth? Fixing BOLA
If Cursor built your backend, there's a good chance any logged-in user can read another user's data just by changing an ID in the URL — here's why that BOLA gap ships by default, how to test for it, and how to close it.
You asked Cursor for an endpoint like GET /api/orders/123, it generated clean, working code, the request returned the right order, and you moved on. That's the trap. "It works" only proves the happy path — it says nothing about whether user A can read user B's order by asking for GET /api/orders/124. That single gap is called Broken Object-Level Authorization (BOLA), and it is the most common serious flaw in Cursor-built APIs. It's not that Cursor is bad; it's that the model optimizes for a route that returns data, and correct authorization is invisible unless you explicitly demand it. The industry data backs this up: roughly 91.5% of AI-generated apps carry at least one flaw, and CMU found that while 61% of AI code is functionally correct, only about 10.5% passes a security review.
What BOLA actually is (and why it's not "just a bug")
Authentication answers "who are you?" Authorization answers "are you allowed to touch this specific record?" BOLA is a failure of the second question. When an endpoint accepts an object ID from the client — an order ID, an invoice number, a user profile ID — and looks it up without checking that the record belongs to the caller, anyone with a valid login can walk the whole table by incrementing the number. Because the attacker is authenticated, nothing looks abnormal: no failed logins, no error spikes, just a logged-in user reading records that aren't theirs. That is why it routinely leaks real customer data, and why OWASP has ranked it the number-one API risk. It also maps directly to the top of the broken access control category that dominates modern security findings.
Why Cursor ships this by default
An AI coding assistant writes the most probable code for your prompt. If you say "build an endpoint to fetch an order," the statistically likely, textbook-shaped answer is: read the ID from the request, query the database for that ID, return the row. The ownership check is an extra clause that the prompt never mentioned, so it's usually omitted. Each per-tool default failure follows the same logic in a different spot: Lovable tends to ship missing or broken Supabase Row-Level Security (RLS); Bolt.new hardcodes secrets into the client bundle that gets shipped to the browser; Replit can expose a public .env on default deployments; v0 leaves API routes unauthenticated. Cursor's signature gap is BOLA — the route is authenticated, but it never confirms the object belongs to the caller. The fix is cheap once you know to look; the danger is that everything appears to work until a stranger reads your customer list.
"It works" means the request returned data. "It's secure" means the request returned only the data that caller is allowed to see. Cursor guarantees the first; only you can guarantee the second.
How to test for it in ten minutes
You don't need a pentest to catch BOLA — you need two accounts. Create user A and user B, each with their own record (an order, a document, a profile). Log in as A and note the ID of A's record. Now, while still authenticated as A, request B's record by its ID — in the browser URL, or with a tool like Postman or curl using A's session token. If you get B's data back instead of a 403 Forbidden or 404 Not Found, you have a confirmed BOLA. Repeat this for every endpoint that takes an ID in the path or query, and don't forget write operations: try to update or delete B's record while logged in as A. The read leaks get the headlines, but an unchecked DELETE is worse.
How to fix it — and keep it fixed
The core fix is one principle applied everywhere: never trust an ID from the client as proof of ownership. On every request, derive the user's identity from their verified session or token — never from a parameter the client can change — and scope every database query by that identity. Instead of "find the order with this ID," ask "find the order with this ID that also belongs to this user," and return a 404 when there's no match so you don't even leak which IDs exist. Push this down to the data layer wherever you can: with Supabase, that's Row-Level Security policies so the database itself refuses cross-tenant reads; with a traditional ORM, it's a shared, tested authorization helper that every handler must call. The goal is to make the secure path the only path, not a rule each new endpoint has to remember. Work through the checklist below.
- Inventory every endpoint that accepts an ID (path or query) and every write operation (POST/PUT/PATCH/DELETE) — this is your BOLA attack surface.
- For each one, add an ownership check: the query must filter by the authenticated user's ID, taken from the session/token, not from the request body or URL.
- Return 404 (not 403) for records the caller doesn't own, so you don't confirm which IDs exist.
- If you're on Supabase, enable and write Row-Level Security (RLS) policies so the database enforces isolation even if a route forgets.
- Prefer unguessable identifiers (UUIDs) over sequential integers so IDs can't simply be incremented — defense in depth, not a substitute for the ownership check.
- Run the two-account test (A tries to read/update/delete B's records) against every endpoint, and add it as an automated test so a future Cursor edit can't silently reintroduce the gap.
- Check nested and related resources too (e.g. /orders/123/items) — the parent may be checked while the child is not.
None of this is exotic — it's disciplined plumbing that AI assistants skip because you didn't ask for it. If you'd rather not audit every route by hand, Comestare's senior software and security engineers do the done-for-you work of fixing BOLA and the rest, then securing, shipping, and marketing your AI-built app so it stays alive after launch.