You shipped something. It works when you click through it.
That is the trap. Clicking through your own app tests the one path you built it to handle. It tells you nothing about the person who edits the URL, taps Pay twice on a bad signal, or the month your AI bill arrives.
Five checks catch almost all of it. Here is the prompt, then what each one is actually looking for.
The prompt
Paste this into Claude Code with your project open. It reports, it does not change anything.
You are auditing my app before launch. Do not write features and do not fix
anything in this pass. Run these five checks. Report PASS or FAIL for each,
with the file and line number. If you cannot verify something, write
"not verified" rather than guessing.
CHECK 1 - CROSS-ACCOUNT ACCESS
Find every route, query and API handler that reads or writes a record
belonging to a user. For each, tell me whether it filters by the
AUTHENTICATED user's id, or only by an id taken from the URL, body or params.
List every handler that trusts a client-supplied id with no ownership check.
If I use Postgres or Supabase, list every table with row-level security OFF.
Output a table: route | ownership check? | file:line
CHECK 2 - DOUBLE SUBMISSION
Find every handler that takes money, creates an order, or sends something.
For each, tell me what happens if it runs twice with identical input.
Say whether it is protected by an idempotency key, a unique database
constraint, or nothing at all. For any Stripe call, check whether an
Idempotency-Key header is set on every POST.
Output a table: handler | protected how? | what a double-tap does today
CHECK 3 - COST PER REQUEST
Find every call to an LLM or a metered third-party API. For each, give the
model, the max tokens, and whether anything limits how often one user can
trigger it. Then calculate, showing your arithmetic:
(a) the cost of one call
(b) the cost of one heavy user making 50 calls a day for a month
(c) the cost if 100 free users each do that
Use current published prices and say which page you took them from.
Finish with the single cheapest place in my code to put a cap.
CHECK 4 - EXPORT AND RESTORE
Give me the exact commands to export all my data, and to restore that export
into an empty database. Then list anything in my stack holding data those
commands would not capture.
Output: the two commands, then the lock-in list.
CHECK 5 - ACCEPTANCE TESTS
Write five pass/fail tests that prove the app works. "The page loads" is not
one of them. Two are mandatory: a payment that FAILS, and a save that is
INTERRUPTED halfway. For each test give the input, the expected end state,
and the exact way to check it.
Output: five runnable tests.Run it before you fix anything. The report is the point — you want the whole list, not the first problem.
What each check is actually looking for
1. Can one user open another user's data?
This is the most common serious bug in software, not a niche one. In OWASP's 2025 Top 10, Broken Access Control is ranked #1, and 100% of applications tested showed some form of it. The list names the exact failure: bypassing access control by modifying the URL, and viewing someone else's account by supplying its identifier.
It happens because /orders/1042 works perfectly in testing. You are logged in as the person who owns order 1042. Nothing in the code checks that — it just looks up 1042 and returns it. Change the number and you have someone else's order.
The fix is one line in the query: filter by the session's user id as well as the record id. The check is making sure that line exists in every handler, not just the ones you remembered.
2. Does tapping Pay twice charge twice?
Someone on a train taps Pay. Nothing visibly happens. They tap again.
Stripe built the answer into the API: send an Idempotency-Key header on the POST. Stripe saves the status code and body of the first request under that key and replays it for any repeat — including a 500. Keys can be up to 255 characters, and Stripe prunes them after at least 24 hours, so the protection covers the retry window that matters. Every POST accepts one. GET and DELETE do not need one.
Generate the key per user action, not per request, or the retry just gets a fresh key and charges again.
3. What does one AI request actually cost?
Not the monthly bill — one request. Until you know that number you cannot tell whether a free tier is marketing or a leak.
Do the arithmetic all three ways: one call, one enthusiastic user for a month, a hundred of them. The third number is the one that matters, because it is the number that arrives before you have any revenue. A cap you can put in one place beats a pricing page you rewrite in a panic.
4. Can you leave?
Not "do you have backups". Backups nobody has restored are a rumour. The check is whether you can run an export and load it into an empty database, and whether it actually comes back.
Do it once, now, while the database is small and nothing is at stake. The failure you want to discover today is a missing extension or a broken foreign key — not the version you discover during an outage.
5. What does "working" mean?
Write it down before you code, in pass/fail terms. A test that says "the page loads" passes on a page that loads and does nothing.
Two tests are non-negotiable, because they are the two nobody writes. A payment that fails — the card is declined, and the user must end up in a recoverable state, not a blank screen or a half-created order. And a save that is interrupted — the browser closes mid-write, and the record must be either wholly saved or wholly absent, never half.
Those two tests are where real money and real data go missing.
One honest caveat
This is a triage pass, not a penetration test or an audit. It catches the failures that show up again and again in small apps shipped fast. It will not find a subtle logic flaw in your pricing, and it is not a substitute for a security review before you hold anyone else's sensitive data.
What it does do is make sure that if your app breaks, it breaks in a way you chose.
Run it and reply to this email with what it found. I read every one, and the strangest FAIL usually makes the next issue.
If you would rather not run it yourself, that is the kind of thing we do at OptiMAX.
Sources, verified 21 September 2026: OWASP Top 10:2025 A01 Broken Access Control · Stripe idempotent requests