
Why vibe coding creates security holes
Vibe coding is brilliant for one thing: getting to a working product fast. You describe what you want, the AI codes it, it runs. Real value, no argument.
The trap is confusing "it works on screen" with "it's ready for real life." The AI writes code that works. Not code that holds up. The difference is everything that doesn't show in the demo: what happens when someone tries to break in, to test a thousand passwords, to tamper with a request?
The AI doesn't have those reflexes. Not because it's bad, but because nobody asks it to. Security doesn't break the demo, so it slips through the cracks.
I audit vibe-coded apps regularly. And I almost always find the same list of holes. Here they are, ordered from most common to most subtle. For each one: what it is, why the AI misses it, and how to check whether your app is affected, even if you're not technical.
1. Secrets written in plain text in the code
The risk. A password, an access key to a service, an API token... written in black and white in the project's files. The worst case I see often: the admin account password, the one that can do anything, tucked into the code. And since code keeps a history of all its versions, even if you erase it today, it stays readable in the project's past.
Why the AI misses it. For a demo to work, the password has to be somewhere. The simplest move for the AI is to write it straight in. It works. It's just that "it works" and "it's safe" aren't the same thing.
How to check. Ask whoever codes for you (or the AI itself): "Are there any passwords, keys, or secrets written directly in the code?" The right answer is that they should all live in environment variables, a place kept separate from the code. Simple rule: if it's in the code, assume it's public.
2. A login with no limit on attempts
The risk. Nothing stops an attacker from testing passwords endlessly, at full speed. It's like an armored door whose codes you can try one by one, as many times as you want, without anyone noticing. With automated tools, thousands of tries per minute, it's just a matter of time.
Why the AI misses it. A login form that works means "I type my password, it lets me in." The AI does that perfectly. The idea of counting failures and blocking after a few attempts is an extra layer nobody asks for.
How to check. "What happens if someone gets the password wrong ten times in a row? Fifty times?" If the answer is "nothing, they can keep going," you've got the hole. You need a lockout after a few failures, and ideally a slowdown.
3. Dependencies never updated
The risk. A modern app is your code plus dozens of ready-made bricks, written by others. Those bricks sometimes have flaws. When a flaw is found, it's published openly, so everyone can update. The problem: attackers read those publications too. A known, unpatched flaw is an open door with the manual posted right next to it.
Why the AI misses it. The AI installs those bricks at a point in time and moves on. It never comes back to check whether a security update has shipped since. And neither do you, if nobody tells you it's a thing to do.
How to check. "When were the app's dependencies last updated?" If the answer is "at creation, never since," it needs handling. It's a quick check, done with free, automated tools.
4. The server that trusts the client
The risk. This is the most important one, and the most technical, so let's use an image. Your app has two parts: what runs on the user's screen (the "client"), and what runs on your server. Anything on the user's screen, they can modify. So if your server trusts what the screen sends it without re-checking, anyone can lie to it.
Concrete example: the app asks the server "give me document number 42." If the server hands it over without checking that the document actually belongs to the person asking, you just ask for 43, 44, 45... and read other people's data.
Why the AI misses it. When the AI codes, it makes the screen and the server talk to each other in one flow. The reflex of "never trust what the client says, always re-check on the server" takes real discipline of suspicion. The AI, by default, trusts.
How to check. This is the hardest to test without looking at the code. Ask it straight: "Does the server systematically re-check that each user is allowed to access what they request, or does it trust what the screen sends?" This is exactly the kind of thing an audit hunts down first.
5. Session tokens stored in the wrong place
The risk. When you log in, the server gives you a kind of access wristband (a "token") that proves it's you, so it doesn't ask for your password on every click. The question is where that wristband is stored in the browser. If it's stored somewhere the page's code can reach, another flaw (see point 6) lets it be stolen. And with your wristband, the attacker becomes you, without ever having had your password.
Why the AI misses it. The simplest way to store that token is also the least safe. The AI takes the shortest path: it works, the user stays logged in. Secure storage takes a slightly finer setup.
How to check. "Where is the login token stored, and can the code running in the page reach it?" Best practice is storage that the page's code can't read directly.
6. User input taken as-is
The risk. Anything a user can type (a name, a comment, a message) can contain, instead of text, hidden instructions. If your app displays that content again without neutralizing it, those instructions can run on other visitors. It's the classic that allows, among other things, stealing the tokens from point 5. Same goes for exports: a spreadsheet file can hide booby-trapped formulas.
Why the AI misses it. Displaying what the user typed is the basic feature. Neutralizing it before displaying is an invisible step nothing in the demo asks for.
How to check. "Is everything users type cleaned before being shown back to others?" It's a standard protection, often provided by modern tools, but one you can bypass without meaning to.
7. Production config left on default
The risk. The code can be flawless but deployed with a careless production config. The classics: "debug" mode left on (it shows technical details that are gold for an attacker when an error happens), error messages that say too much, or the app running with full powers over the server (so if it gets compromised, the attacker walks straight out onto the whole machine instead of staying stuck inside the app).
Why the AI misses it. Deployment config isn't application code. It's the environment around it. The AI generates the app, rarely the clean way to put it online. And debug mode, precisely, is super handy during development, so it often stays on by oversight.
How to check. "Is debug mode off in production? Do the error messages shown to users stay vague? Does the app run with the minimum rights on the server?"
The good news: none of these holes is hard to close
If you're reading this list and feeling the anxiety rise, breathe. Here's what I observe in every audit: these holes are omissions, not design flaws. Boxes never ticked because nobody knew they existed. Not an app to rewrite.
Most of them close in a few hours to a few days, not weeks. Pulling a secret out of the code: ten minutes. Blocking login attempts: half a day. Updating dependencies: half a day. The hardest, point 4, takes a real review, but stays largely fixable.
And the real message is this: if the core of your app was well built (and AI-generated code often is, more than people think), these holes are the finishing layer. Not the foundations. You close them, and you go from "it works" to "it's solid."
Want to know which of these holes are in your app?
This list gives you what you need to ask the right questions. But to really know what's hiding in your code, you have to read it. That's what I do: I run your app through the wringer, list the real risks ranked by severity, and give you a prioritized fix plan, from "do today" to "this can wait." Check out the Audit offer.
And if you'd like this kind of checklist straight in your inbox, sign up for the newsletter. An email now and then, all substance, zero spam.

