Lovable, Bolt, Replit, v0 and Cursor will get you from an idea to a deployed, working product in a weekend. That used to take a month. This is genuinely good.
But they optimise for it renders. Taking money needs it holds. Those are different bars, and the gap between them is where I spend my working life.
Here are the seven checks I run in the first hour of every rescue. Run them on your own app right now - all of them are free and most take under a minute.
1. Search your client bundle for secrets
Open your live site, view source, open the JS bundles it loads, and search for key.
A client bundle is public. Whatever your dashboard calls it, a key shipped to the browser belongs to everyone who visits.
The one that hurts most is Supabase's service_role token, because it looks exactly like the anon token that is supposed to be there. Both are JWTs. Decode the payload and look:
const payload = JSON.parse(
Buffer.from(jwt.split('.')[1], 'base64').toString('utf8')
);
console.log(payload.role);
// 'anon' -> fine, this one is meant to be public
// 'service_role' -> this bypasses every row-level security policy you have
If it says service_role, rotate it today and move it behind your own server route.
2. curl your own API with no auth token
curl -i https://yourapp.com/api/orders
If that returns data, your authentication is UI-only: the login screen hides the button, not the endpoint. This is the single most common hole I find, and it is invisible from the browser because your frontend always sends the token.
The fix is boring and unavoidable - check the session inside every route, not in the component that renders the link.
3. Check row-level security on every table, not the ones you tested
AI tools scaffold tables fast, and RLS is off by default. You tested profiles. Did you test invoices, the one you added at 1am three weeks later?
One table without RLS means any logged-in user reads every row in it, including other customers' rows.
4. Verify your webhook signatures
If your /api/webhooks/stripe route trusts the request body, anyone who discovers that URL can POST it and mark an order paid. You will ship goods for money that never arrived.
import crypto from 'node:crypto';
const expected = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(rawBody) // the RAW body, not the parsed JSON
.digest('hex');
const ok = crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureFromHeader)
);
if (!ok) return new Response('bad signature', { status: 400 });
Two things people get wrong here: comparing with === instead of a timing-safe compare, and hashing the parsed body instead of the raw one.
I got tired of the ngrok-and-dashboard loop needed to test this, so I published razorpay-trigger - a zero-dependency CLI that fires correctly signed webhooks at localhost so your handler is testable in CI.
5. Does a failed payment roll the order back?
Create an order, force the payment to fail, then look in your database. If there is a row sitting there marked paid, you have a reconciliation problem that grows quietly until the day you check your bank balance against your dashboard.
6. Rate limit three things
Signup, password reset, and above all the LLM call that bills you per token. An unmetered AI endpoint is somebody else's free API, paid for with your card.
7. Run one test before deploy
Not a test suite. One test. One is infinitely more than zero, and it is the thing that stops the 2am regression that breaks checkout.
I automated the four a machine can actually check
Checks 1 and 2 are tedious by hand, and nobody does them twice. So I built a scanner:
Paste a URL. In about 20 seconds it fetches the public page and the same-origin scripts that page already tells a browser to load, then reports:
- secret keys in the client bundle - Stripe, OpenAI, Anthropic, AWS, Google, GitHub, Slack, SendGrid, Razorpay, private key blocks, and Supabase
service_roleJWTs decoded and role-checked - publicly reachable source maps
- missing security headers (CSP, frame protection, HSTS, nosniff, referrer policy)
- cookies without HttpOnly or Secure
- direct Supabase or Firebase access from the browser
- which tool built the app
It is free, needs no signup, and stores nothing. It is also strictly passive: it makes the same requests any visitor's browser makes, never probes private routes, never attempts a login, and never writes anything.
A clean score means nothing reachable from outside was found. It does not mean your app is secure - checks 2 through 7 above live behind your login, and no external scanner can see them.
None of this is an argument against building with AI. It is an argument for one hour of review before the first real payment lands.
If you want that hour done properly on your app, I do it for a flat $149 with the report back in 48 hours - I'm @Ujjvalbhatti007.
Top comments (0)