I'm not a security person.
I built a small paid SaaS mostly by prompting AI — Cursor and Claude Code doing most of the typing, me steering. It's a tool people pay for, with the payment processor checkout wired in. And right before I opened it up to real users, I got nervous.
Not because anything looked broken. Everything worked. The demo was clean, the checkout went through, the paid features unlocked. That was exactly the problem: it worked, and I couldn't fully read my own codebase to tell whether it was actually safe. "Works" and "safe to put real money and real user data behind" are not the same sentence, and I only half-understood the code standing between them.
So before launch, I sat down and audited it myself — line by line through the parts that could actually hurt me. Here's what I found, including the part that surprised me.
The stuff I was scared of
If you've read any "AI writes insecure code" thread, you know the usual suspects, and they were exactly what I was afraid of:
- Could someone change the price in their browser and pay $1 instead of $29?
- Was my paywall real, or could anyone just skip payment and grab the paid output?
- Did the AI quietly paste my secret keys somewhere the browser could see them?
- Was there some "skip payment" dev shortcut still live in production?
These are the ones that kill you quietly. Not a dramatic hack — just a user who pokes around, changes one value, and gets your paid product for free while you're asleep.
The part that surprised me
The payment path was solid.
The price was enforced on the server — the client only sends which plan you picked, never the amount, so there's nothing to tamper with. After the payment processor captured the payment, the code actually verified it: right status, right amount, right currency, before handing anything over. The token that unlocks the paid report was cryptographically signed, so you can't forge one. And that "skip payment" shortcut I was worried about? It was locked to the development environment — in production it's simply off.
I half-expected a mess and didn't find one.
I'm saying that on purpose, because most "AI code is insecure" takes skip the part where sometimes it's fine. If I told you everything was broken, I'd be doing the thing security people do to sell you something — making you scared. It wasn't broken. Whoever wrote it (me, plus a lot of AI) got the money path right. Credit where it's due.
Which is exactly what made the two real problems interesting. They weren't where I was looking.
The two things that actually would've gotten me
One: my free feature could drain my own bill.
The app has a free step that calls an AI model — that's the hook that gets people in the door. It's supposed to be rate-limited so nobody can hammer it. But when I read the limiter carefully, there was a hole: if one specific piece of infrastructure wasn't configured just right, the rate limit didn't tighten — it switched off entirely. No limit at all. And the thing it was keying off of could be swapped by the user sending the request.
Translation: someone with a simple script could call my free AI feature over and over and run up the bill I pay to the AI provider. No hack, no login, no trace. Just my own credit card, quietly emptying.
Two: a backup file full of live keys was one careless zip away from leaking.
I had a "backup" copy of my environment file sitting in the project — the one with the real, live keys in it. It was ignored by version control, so it never hit my repo. But it was still sitting right there in the project folder, which means the moment I zipped the project to share it, or moved it somewhere, those keys were riding along. That's not a hack either. That's a habit. And habits like that are how keys end up somewhere they shouldn't be.
Here's the part that stuck with me: a scanner would've flagged neither of these. I ran the automated tools too. One problem was runtime logic — a limiter that fails the wrong way under one condition. The other was a habit — a file in the wrong place. Tools are great at the mechanical stuff, the hardcoded-key-on-line-42 stuff. These two needed someone actually reading the money-and-abuse path and asking "okay, but what happens when this piece isn't there?"
What I didn't check
To be straight with you: this wasn't a full penetration test. I didn't attack the live site, I didn't read every line of the longest file, and I didn't audit the payment processor account config. This was me reading the parts most likely to cost me money or leak data before launch — not a certificate that says the whole thing is bulletproof.
Fixing the two issues took an afternoon. Rotating the keys that had been sitting in that backup took ten minutes. Now the thing can take money without me lying awake about it.
If you built something like this
If you've shipped — or are about to ship — something you built with AI that takes money or calls an API, these two classes are worth checking yourself, because they're the ones the tools stay quiet about:
- Does your free, AI-calling feature have a rate limit that fails closed — off means blocked, not unlimited? And is it keyed off something a user can't just swap?
- Is there any file with real keys in it sitting inside your project folder, one zip away from going somewhere public?
I wrote up the full checklist I used, in plain English, here:
https://gist.github.com/haiyangr-cmyk/cd98502fa007b07d1807af1516f3e13a
And run a free scanner while you're at it — SafeToShip, Semgrep, whatever fits.
They'll catch the mechanical stuff (exposed keys, headers) in two minutes, and
you shouldn't pay anyone before you've done that.
What they won't catch is the two classes above: the runtime logic (a limiter
that fails the wrong way) and the permission/payment logic (who can do what,
can the price be tampered). Those need a person reading the money-and-abuse
path. That's the part I do now — after the scanner, not instead of it. If your
app takes money or holds user data, drop a comment or reach out.
Either way: check the free-endpoint one. It's the quiet one, and it's the one
that bites AI-built apps the most.
Top comments (0)