Vibe coding is amazing. You describe what you want, the AI writes it, it works on localhost, and you feel like a 10x developer.
Then you push to production with real users and real data, and things get ugly.
I've shipped a few projects this way, and I've learned — once at the cost of real money — that AI-generated code is great at making things work and terrible at making things safe. The AI optimizes for "it runs" — not for "it survives the internet."
Here are the three things I now check every single time before going live. None of them are complicated, and skipping them is how you end up leaking user data.
1. Exposed environment variables (this one cost me real money)
Let me tell you how I learned this.
I run AskLeya, a SaaS chat assistant for businesses. It uses an OpenRouter API key to call GPT-4o, and that model has exactly one job: read the customer's knowledge base and answer their customers' questions. That's it. One model, one purpose.
One day a customer flagged that their chat widget had stopped responding. I checked the code. No errors. I checked the server. Fine. Then I opened the OpenRouter dashboard.
My credit — which by my usage math should have lasted a long time — was gone. And the usage log showed calls to Claude Opus, Claude Sonnet, Kimi, GLM and a handful of other models I had never touched. Someone had my key and was using it as their personal free AI account.
I rotated the key immediately and the app came back. I didn't spend more time hunting for exactly how it leaked — at that point the fix was the same regardless. But here's the uncomfortable part: I wasn't even fully vibe coding that project. I used AI help in some areas, wrote the rest myself, and I still got caught.
The likely suspects were the usual ones: a key that ended up in a client-side bundle, a .env that briefly touched a git commit, or a key pasted into a chat/tool while debugging. Any of them is enough.
Two things I do differently now:
- Set a spend limit and a model allowlist on every key. OpenRouter (and most providers) let you cap credits per key and restrict which models it can call. If my key had been limited to GPT-4o with a small ceiling, the damage would have been capped and the alerts would have fired early. Do this on day one, not after.
- Treat every key as already leaked. Ask "what happens if this gets out?" and design so the answer is "not much."
Now, the general checks:
Your AI assistant needs an API key to make something work. So it puts it somewhere convenient. Sometimes that "somewhere" ends up in the browser.
Things to check:
-
Client-side prefixes. In Next.js, anything starting with
NEXT_PUBLIC_is shipped to the browser. In Vite it'sVITE_. If your OpenAI key, Stripe secret key, or database URL has one of these prefixes, it's public. Anyone can open DevTools and copy it. -
.envcommitted to git. Check that.envis in your.gitignorebefore your first commit, not after. If it's already been pushed, rotating the keys is the only fix — deleting the file doesn't remove it from history. -
Keys hardcoded in the code. AI tools sometimes paste the actual key into a file "temporarily." Grep your codebase for
sk-,pk_,AKIA, and anything that looks like a token. - Server vs client boundary. Any call that uses a secret should happen on the server (API route, server action, edge function). If your frontend is calling a third-party API directly with a secret, that's a leak.
Quick test: build your project, open the output folder, and search the bundled JS for your keys. If you find one, you have a problem.
2. No rate limiting
Your app works fine when you're the only user. Then someone writes a 10-line script that hits your signup endpoint 50,000 times, or your AI chat endpoint 5,000 times, and you wake up to either a crashed app or a $400 API bill.
AI-generated code almost never adds rate limiting unless you ask for it.
Where you need it most:
- Auth endpoints — login, signup, password reset. Without limits, these are open to brute force and spam accounts.
- Anything that costs you money per request — LLM calls, email sending, SMS, image generation. This is the one that hurts your wallet.
- Public forms — contact forms, comments, anything unauthenticated.
- Expensive database queries — search, exports, reports.
You don't need to build this yourself. Most platforms have a one-line solution:
- Vercel / Next.js:
@upstash/ratelimit - Express:
express-rate-limit - Supabase: enable rate limits in Auth settings
- Cloudflare: rate limiting rules in the dashboard, no code needed
Even a crude limit like "10 requests per minute per IP" is 100x better than nothing.
3. OWASP vulnerabilities
OWASP publishes a Top 10 list of the most common web security holes. It's been around for years, and AI models were trained on plenty of code that ignores it — so your generated code probably does too.
You don't need to memorize the whole list. These are the ones I see most often in vibe-coded projects:
-
Broken access control. The API checks if you're logged in, but not if you're allowed to see this specific record. Change the ID in the URL from
/orders/123to/orders/124and you're reading someone else's data. This is the #1 issue on OWASP's list and the #1 thing I find in AI-generated apps. - Injection. If any user input goes into a SQL query, shell command, or HTML without being sanitised or parameterised, you're exposed. Using an ORM helps but doesn't make you immune — raw queries sneak in.
- Missing input validation. The frontend validates the form, but the API accepts anything. Validate on the server with something like Zod or Joi. The frontend is not a security boundary.
-
Insecure defaults. Debug mode on in production. CORS set to
*. Database with no row-level security. Admin routes with no auth check because "nobody knows the URL."
The fix is simple but boring: for every API endpoint, ask "who can call this, and what can they get?" Then actually test it — log in as user A and try to fetch user B's data.
Before you push
Here's the checklist I run through:
- Searched the built bundle for secrets
-
.envin.gitignore, no keys in git history - All secrets used server-side only
- Rate limits on auth, paid APIs, and public forms
- Every endpoint checks ownership, not just login
- Server-side validation on all inputs
- Debug mode off, CORS locked down
Takes maybe an hour. Saves you from a very bad week.
One more tip: you can literally paste this list into your AI assistant and say "audit my codebase for these." It's surprisingly good at finding the problems — it just doesn't fix them unless you ask.

Top comments (0)