August 25, 2026
Your vibe-coded app probably has a door open
The best thing about vibe coding is that you get a working app before you lose interest. The worst thing is that “working” and “safe” are…

By Dennis Sev7n
3 min read
The best thing about vibe coding is that you get a working app before you lose interest. The worst thing is that "working" and "safe" are two different tests, and the model is only being graded on the first one.
I don't say that to scare anyone off. Build the thing. Ship the thing. But somewhere between the demo and the first real user, it's worth spending twenty minutes closing the doors you didn't know you left open. Here's the short list.
The AI writes for the person who behaves
Every prompt you give is implicitly about a well-behaved user. Sign up, log in, click the button, see the dashboard. So that's the code you get back: the happy path, beautifully done.
Security is the unhappy path. It's the user who doesn't sign up and calls your API directly. The one who changes the number in the URL. The one who sends a 4MB string into a field that expected a first name. Nobody asked for that code, so nobody wrote it.
The fix isn't complicated: you have to ask. Which brings me to my favourite trick.
Make the model attack its own work
After you've got a feature working, open a fresh chat, paste the code, and say something like: you're a bored attacker with an hour to kill. What can you do with this endpoint that I didn't intend?
It's shockingly good at this. The same model that cheerfully wrote the vulnerable code will happily list the ways in, because now you've asked the right question. Do this per feature, not once at the end.
Your keys are in the browser
This is the one that bites people fastest. You ask for an app that calls OpenAI or Stripe or Resend, and the model puts the API key in the frontend because that's where the fetch call lives.
Anything in your frontend bundle is public. Not "hard to find" — public. Someone opens devtools, hits the network tab, and there it is. People run scrapers that hunt for exactly this, and the first sign of trouble is usually a bill.
Rule of thumb: if a key can spend money or read data, it goes on the server. Your frontend calls your backend, and your backend holds the key. And if you've ever committed a .env file, rotate those keys — git remembers everything, even after you delete the file.
"Logged in" is not the same as "allowed"
Here's the classic vibe-coded bug. The app hides the admin button unless you're an admin. Looks right. Works right. But hiding a button is a UI decision, not a security one. The endpoint behind it is still sitting there, answering anyone who asks.
Same story with /api/orders/1234. If your server fetches order 1234 without checking that it belongs to the person asking, then I can read your entire orders table by counting upward. This is the single most common flaw in apps I've poked at, and it takes one line to fix: check ownership on the server, every time, for every record.
Client-side checks are for user experience. Server-side checks are for security. You need both, and they're not interchangeable.
Turn the locks on in your database
If you're on Supabase or Firebase, this one deserves its own paragraph, because the tutorial gets you running with the doors wide open and never quite tells you to close them.
Supabase has row-level security. Tables you create through the dashboard get it switched on for you, but tables created with raw SQL don't, and raw SQL is exactly what the AI hands you. RLS on with no policies written means nothing gets through; RLS off means the anon key sitting in your frontend can read and write whatever it likes. Firebase is similar: test-mode rules let anyone read and write for 30 days, then stop working entirely.
Both are good tools. Both will happily serve your users' data to a stranger with curl until you write the policy that says otherwise.
Go check yours right now. I'll wait.
Don't hand-roll the boring stuff
Auth, password hashing, session tokens, password resets. These look simple and they are not, and there is no prize for writing your own. Use Clerk, Auth0, Supabase Auth, NextAuth, whatever fits — people who think about this full-time have already made the mistakes for you.
The same goes for rate limiting, which the model will basically never add unless you ask. Without it, your login form is a free brute-force target and your AI endpoint is somebody else's free AI endpoint.
A twenty-minute pass before you ship
Not a security audit. Just a walkthrough:
Open devtools and search your bundle for anything that looks like a key. Log out, then try hitting a protected API route directly. Log in as one test user and try to read another test user's data by changing an ID. Check that your database has RLS or rules turned on. Confirm your production build isn't printing stack traces to strangers. Update your dependencies.
That's it. Six things, most of them a minute each.
None of this makes you a security engineer, and it isn't meant to. It just means that when your app finds real users, the obvious doors are shut. Vibe coding got you the app in an afternoon. You can spare twenty minutes for the locks.