2026-07-21 · it-strategy

You Vibe Coded Your Way Into a Security Nightmare ... And You Don't Even Know It Yet

I was sitting in a conversation recently at a meeting with another company's executives ... the kind of meeting where someone mentions something almost offhand, like it's obvious, and you're left quietly trying to decide if you heard that right. The thing they said, essentially, was this: they were replacing their CRM ... an enterprise-grade system holding all of their customer information, purpose-built, tested, with a vendor behind it ... with something they had vibe coded.

Just like that.

I didn't ask what "vibe coded" meant. I know what it means. You probably do too. AI-assisted development where you describe what you want in natural language, the model spits out code, you tweak it until it mostly does the thing, and then you ship it. No formal architecture review. No security design. No threat modeling. Possibly no one on the team who could read the output code critically, let alone audit it.

That code now holds every customer contact, every conversation, every deal, every piece of strategy tied to those relationships.

I'm not writing this to be dramatic. I'm writing this because that conversation stuck with me in the way that things stick with you when you've been around long enough to know how the story ends. I've been in environments where someone made a decision that seemed fast and cheap and clever, and then spent the next eighteen months explaining it to customers, regulators, and occasionally law enforcement. If this were my organization, I would much rather write the warning to leadership now than be the one writing the post-mortem later.

So let me be direct about what's actually happening here, what the real risks are, and ... because I'm not naive enough to think this is going to stop ... what you should at minimum do if you insist on going down this road.

"Vibe coding" is Andrej Karpathy's term. He used it to describe a development mode where you lean fully into AI code generation, riding the vibe of what the model produces rather than exercising the traditional engineering discipline of reading and understanding every line. You describe the intent. The AI writes the implementation. You test loosely, iterate on what feels off, and ship.

There is nothing wrong with this in narrow, low-stakes contexts. Building a personal utility? A script that only you run on your own machine? Fine. The risk surface is limited, the consequences of failure are yours alone, and the speed is genuinely impressive.

The problem starts when this development mode gets applied to software that touches other people's data ... customer records, payment flows, authentication systems, health information, business-critical integrations. That is an entirely different category of risk, and the methodology doesn't scale to it. Not because AI is bad at writing code (it's often remarkably good), but because security is not a feature you add after the fact. It is a property of the design.

Let me say that again more plainly: security is not something you bolt on at the end. It has to be built into the architecture from the start ... the data model, the authentication flow, the way trust is established between components, the way secrets are handled, the way errors are surfaced (or not surfaced) to users. Vibe coding, by its nature, is not a design-first process. It is a result-first process. And in security, that ordering matters enormously.

I want to be specific here because I think the security community does itself a disservice when it speaks in abstractions. "There are risks." Okay. What risks? Let me walk through the ones that actually keep me up at night.

The single most common category of vulnerability in custom-built web applications is broken access control. It's been sitting at or near the top of the OWASP Top 10 for years. It shows up in enterprise software. It shows up in open source projects. It absolutely shows up in vibe coded applications. Authentication ... verifying who you are ... and authorization ... verifying what you're allowed to do ... are genuinely hard to implement correctly. There are subtle decisions that have to be made consistently, everywhere, across every route, every API endpoint, every function that touches sensitive data. You have to think about: what happens if someone is authenticated but tries to access another user's data? What happens if a token is valid but the associated user has been deactivated? What happens when a session expires and the client retries? What if someone manually crafts a request and changes an ID in the URL from their own to someone else's?

When you vibe code, the AI will typically generate authentication that looks correct. It will produce something plausible ... "plausible" and "correct" are not the same thing, and in access control, the gap between them is where breaches live. The AI has no idea what your specific trust model is. It doesn't know whether users should be tenant-isolated or not, whether your admin roles are additive or restrictive, whether there's an assumed identity problem lurking in how you pass user context between services. It will make guesses. Some of those guesses will be wrong. I've seen this produce applications where changing a single query parameter in a URL gives you access to a different customer's records. That's not theoretical. That's Insecure Direct Object Reference, and it is embarrassingly common.

Every web application that accepts user input and does something with it ... stores it, displays it to other users, passes it to a database, feeds it to another system ... needs to treat that input as untrusted. Always. Without exception. Vibe coded applications frequently fail this. The AI will produce code that takes user input and does things with it: inserts it into a database query, renders it in a template, passes it to a shell command, serializes it into a log file. What the AI often doesn't do consistently is validate, sanitize, and encode that input at every boundary where it crosses from one trust domain to another. The consequences of getting this wrong span decades of well-documented attack categories. SQL injection allows attackers to manipulate database queries directly. Cross-site scripting (XSS) allows attackers to inject malicious scripts into pages rendered by other users ... stealing sessions, redirecting users, exfiltrating data. Command injection, XML injection, path traversal, server-side template injection. These aren't exotic techniques. They're in every attacker's standard toolkit precisely because they keep working. The AI generating your code has read about all of these. But it doesn't apply mitigations consistently, especially when the codebase evolves incrementally through natural language prompts. You add a feature here, tweak a function there, and somewhere along the way, an input path gets introduced without the same care that was applied to the original code. This is the nature of iterative AI-assisted development: the consistency of security controls degrades over time.

Then there's secrets management, which is almost embarrassingly predictable and yet happens constantly. When you're vibe coding, you're usually moving fast. You need to connect to a database. You need an API key for a third-party service. You need credentials for something. The AI helpfully generates code with placeholder values like YOUR_API_KEY_HERE, you swap in the real value, and you ship. Where did you put it? In a config file. Where is that config file? In the repository. Where is the repository? On GitHub. Is the repository public, or does it have a history that was once public, or was the organization's visibility settings changed after the fact? This is not a hypothetical scenario. It is how a stunning volume of real-world credential exposures happen. AWS keys, database passwords, Stripe secret keys, OAuth client secrets ... they end up in version control with alarming regularity. Some stay there for years before anyone notices. Some are found by automated scanners within minutes of being pushed. Vibe coded applications have a particular exposure here because the developer is often working alone, moving fast, and not following a secure development lifecycle that would catch this in a code review. There often is no code review. That's kind of the point.

If your vibe coded application handles any kind of sensitive data ... passwords, tokens, personal information, health records, payment data ... it needs cryptography. And if it needs cryptography, there is one rule that has been true for as long as I've been doing this: don't implement your own. Cryptographic implementations are extraordinarily easy to get subtly wrong in ways that look fine but provide no real security. Reusing initialization vectors. Using ECB mode for block encryption. Implementing a custom hash that seems reasonable but is trivially breakable. Using MD5 or SHA-1 for password hashing (not what those algorithms are designed for). Generating random tokens with a predictable seed. AI models will sometimes generate cryptographic code that uses the right library but the wrong configuration, or uses a deprecated scheme because there's more training data for it, or just subtly misuses an API in a way that compiles and runs but produces insecure output. Unless someone on your team can audit that code against current cryptographic best practices, you will not catch it. You won't catch it by testing the application's behavior, because it'll appear to work correctly. The flaw is in the math, not the user experience.

Error handling gets less attention but it matters just as much. When your application throws an error, what does it show? Stack traces? Database schema details? Internal server paths? User-specific data? In a vibe coded application, the error handling is often whatever the AI produced by default ... which is usually designed for developer convenience (show everything) rather than security (show nothing useful to an attacker). Detailed error messages are a gift to someone doing reconnaissance on your application. They reveal what database you're using, what framework, sometimes even what version. They reveal the structure of your code. They make every other attack faster and easier. Production applications should show generic error messages to users and log detailed information internally ... and that internal logging should be protected, not sitting in a publicly accessible log file.

And then there's the dependency problem that almost nobody talks about enough. When an AI generates a web application for you, it's generating code that relies on third-party libraries and packages. npm modules. Python packages. Whatever the framework ecosystem uses. These dependencies are not static. They have their own vulnerabilities. They get updated. The version the AI was trained on might have known security flaws. The AI has no way of knowing what vulnerabilities were disclosed last week. A vibe coded application with no ongoing dependency management is accruing technical and security debt from the moment it ships. No one is running npm audit on a schedule. No one has subscribed to vulnerability notifications for the packages in use. No one has a remediation process for when a critical CVE drops in a library you depend on. When that vulnerability is exploited ... and eventually it will be ... the response is always some variation of "we didn't know we were using that." That's not a defense. That's a description of an unmanaged software supply chain.

Here's what concerns me most about all of this, and I want to be clear-eyed about it. If you don't have a security background, you will not recognize most of what I've described above as a problem in the code the AI produces. The code will look fine. The application will behave as expected. It will pass your functional testing. Your users won't immediately notice anything wrong. The security properties of software are largely invisible to normal use. You cannot look at an application and see that it has an SQL injection vulnerability. You cannot tell by clicking through the UI that sessions are not properly invalidated on logout. You cannot observe from user experience that the password reset flow is susceptible to enumeration attacks. These flaws exist in the interaction between your code and an adversarial user, and unless you are actively testing for them ... deliberately trying to break your own application the way an attacker would ... you won't find them before someone else does.

This is why the enterprise tools your organization was using exist. Not because enterprises are in the business of charging you money for things you could build yourself (well, not only because of that). But because there is a body of work ... secure design, threat modeling, code review, penetration testing, dependency management, incident response capability ... that is genuinely hard to replicate and genuinely necessary when software touches customer data at scale. I'm not saying enterprise software is always secure. I've seen enough breaches in enterprise-grade systems to have no illusions about that. But there is a difference between imperfect security and no security. And when you replace a vetted enterprise tool with something vibe coded in a sprint, that's often the trade you're making.

I'm a practitioner, not an idealist. I know this is going to happen. It's already happening. And I'd rather give you something useful than stand on a soapbox and tell you it's a bad idea (even though I think it's a bad idea). So if you are going to build something with AI assistance that is going to touch real users and real data, here is what I'd consider the minimum responsible approach.

Before you write a single line of code, sit down and think about what you're building and what could go wrong. This doesn't have to be a formal exercise with a STRIDE diagram and a risk register. It can be a conversation with yourself that answers these questions: What data does this application handle? Who should have access to it, and who shouldn't? What's the worst thing that could happen if someone malicious got access? What would a bad actor try to do with this application? Write those answers down. Then, when you're prompting the AI to build things, refer back to them. "Build me a user management system that ensures users can only see their own records, not other users'." "Build me a password reset flow that doesn't reveal whether an email address exists in the system." You're not going to get perfect security this way, but you'll get better security than if you never thought about it.

The single highest-leverage thing you can do when vibe coding a web application is to not build authentication yourself. Use an established identity provider ... Auth0, Okta, Cognito, Firebase Auth, whatever fits your stack. These products have teams of engineers who think about nothing but authentication security. They handle session management, multi-factor authentication, token rotation, account lockout, brute force protection. Let them do that. You outsource the most dangerous part of your application to people who are good at it. Similarly, if you need to handle payments, use a payment processor that handles the card data. If you need file storage, use a platform service with built-in access controls. The goal is to minimize the attack surface of the code you're actually writing. The less sensitive code you write, the less sensitive code can be wrong.

On database queries: this is not optional. Every database query in your application should use parameterized queries or an ORM that handles this automatically. Never, under any circumstances, build a query by concatenating user input with a string. If you see the AI generate code that does this, reject it and ask it to rewrite using parameterized queries. This single practice eliminates the entire class of SQL injection vulnerabilities. When you prompt the AI, be explicit: "Write all database queries using parameterized queries, never string concatenation with user input." It will generally comply. Verify that it did.

On secrets: no credentials in your code. No credentials in your repository. Use environment variables for all secrets, and for anything more serious than a weekend project, use a proper secrets management solution ... AWS Secrets Manager, HashiCorp Vault, Azure Key Vault. Most cloud platforms now offer built-in secrets management. Use it. Before your repository ever becomes a repository ... before the first commit ... add your configuration and secrets files to .gitignore. If you're on a team, treat a committed secret with the same urgency as a fire alarm: rotate the credential immediately, assume it was already seen, and figure out how to prevent it from happening again.

There are tools that will scan your code for common security issues without requiring a human security expert to review every line. Run them. Set them up as part of your development process, not as an afterthought. For dependency vulnerabilities: npm audit for Node, pip-audit for Python, Dependabot if you're on GitHub. Run these on a schedule, not just once. For code vulnerabilities: Semgrep, SonarQube, Snyk ... all have free tiers that are useful for small projects. For web application vulnerabilities: OWASP ZAP is free and will run a basic active scan against your running application. These tools are not perfect. They produce false positives. They miss things. But they will catch common, well-understood issues that a vibe coded application is likely to contain, and they cost you almost nothing to run. There is no good argument for not using them.

Before you ship, go through your application and replace every generic error handler with something that logs the full detail internally and shows the user only a generic message. "Something went wrong. Please try again." Full stop. No stack traces. No database errors. No server paths. Log the detailed error to an internal logging system ... not a publicly accessible URL ... and if possible, alert on patterns that suggest someone is actively probing for vulnerabilities.

If you have any budget at all for this, get a penetration test before you go live with anything that handles real customer data. Even a lightweight web application penetration test from a credible firm will find things you didn't know were there. It is not a luxury. It is table stakes for anything customer-facing. If a formal penetration test isn't in the budget, at minimum find someone who has a security background and ask them to try to break your application for an afternoon. Pay them for their time. Buy them lunch. Compensate the knowledge exchange somehow. The alternative is finding out what they would have found in a breach notification.

And plan for when it goes wrong, because this part almost never gets talked about. What is your incident response plan? If something goes wrong ... if you discover you've been breached, if a user reports that they can see another user's data, if you find credentials exposed in your repository history ... what do you do? Who do you call? What do you preserve? What do you notify, and when, and to whom? Depending on what data your application handles and where your users are located, you may have legal notification obligations ... breach notification laws in most U.S. states, GDPR in Europe, various sector-specific regulations. Not knowing about those obligations doesn't exempt you from them. At minimum: know what data you have, know where it lives, have a way to revoke credentials and rotate secrets quickly, and know who you'd call if things went sideways. If you handle data that falls under a regulatory framework, talk to a lawyer before you ship, not after.

I want to end with something that I think is true and that most people in my position won't say directly. When an executive decides to replace an enterprise tool with something vibe coded, they are making a risk decision. Sometimes that's a conscious, informed decision where they've thought through the exposure, have appropriate controls, and are accepting a calculated risk. More often, in my experience, it's a decision made without full awareness of what's being traded away. Speed and cost are visible and immediate. Security risk is invisible until it isn't.

The customers whose data lives in that application didn't make that decision. They didn't choose to have their information in a system built over a weekend with an AI assistant and no security review. They trusted the organization handling their data to make responsible choices. That's not a vibe. That's an obligation.

I'm not saying don't use AI to write code. I use it. It's remarkable. I'm saying: know what you're building, know who it affects, and make security decisions proportional to the stakes. A customer-facing application handling business data is not a personal project. It deserves to be treated like what it is.

The vibe check on this one should probably be: is the speed we're gaining worth the exposure we're creating for the people who trust us with their data?

For most of the decisions I've seen made under the banner of "we vibe coded it," the honest answer is no.