BrainGrid
How-tos

Vibe Coding Security: The Holes Aren't in the Code, They're in the Spec

Vibe coding security fails because the common holes, missing auth, no row-level security, hardcoded secrets, are unspecified acceptance criteria, not scanner findings.

BrainGrid Team
10 min read
Vibe Coding Security: The Holes Aren't in the Code, They're in the Spec

Every article about vibe coding security wants to sell you a scanner. Almost none of them mention that the scanner is looking for a decision you never made.

That is the part the security-vendor blogs skip. When a vibe-coded app ships without authentication on an admin route, or with a Supabase table wide open to the internet, or with an API key sitting in the client bundle, the tempting story is that the AI "wrote insecure code." It didn't, exactly. It wrote code for a requirement you never gave it. You asked for a dashboard. You did not say who is allowed to see it. So the agent picked, and it picked the version that demos fastest: everyone.

That distinction matters because it changes where the fix lives. A scanner catches the hole after it exists, in code you now have to read and understand to trust. Defining the behavior up front prevents the hole from being generated at all. The hypothesis of this post is simple: most vibe coding security failures are missing acceptance criteria wearing a security costume, and the durable fix is upstream of any tool.

#The community already diagnosed this

You do not have to take our word for it. Go read what builders are actually saying in the last few weeks. On r/vibecoding, someone asked the whole community, flatly, how are you handling security in your vibe-coded projects today? The replies are not "which scanner." They are people realizing nobody told the agent the rules.

An engineering manager who reviewed 25 vibe-coded apps put it bluntly: the same issues show up in all of them. Not twenty-five creative new vulnerabilities. The same handful, over and over. Missing auth. No row-level security. Secrets in the repo. Permissive defaults. When the same holes appear in every app built by different people with different tools, the problem is not the tool. It is that none of them said what secure meant before they hit build.

One builder on Instagram, of all places, said the quiet part out loud:

Most AI-generated apps aren't hacked because of AI, they're hacked because basic security is skipped.

Skipped, not failed. You cannot skip a step you were required to take. The reason auth gets skipped is that "users must log in to see their own data" lived in your head as an obvious assumption instead of on the page as a criterion the build gets checked against.

#The scanner-shaped hole in the advice

Search "vibe coding security" and the first page is a wall of vendors: Checkmarx, Wiz, Oligo, Databricks, GuidePoint, Contrast. Their advice converges on one move: shift security left, scan the output, treat AI code as untrusted. The AI Overview at the top of that same search even lists the exact failure modes, missing authentication, no row-level security policies, hardcoded secrets, overly permissive access controls.

That advice is not wrong. It is incomplete in a specific, expensive way. "Treat AI code as untrusted and review all of it" is sound in principle and brutal in practice, because the person who vibe-coded the app is frequently the person least equipped to read it. If you could confidently audit the auth logic line by line, you probably would not have needed to vibe-code it in the first place. The scanner tells you a hole exists. It does not tell you what the right behavior was, and it cannot, because you never wrote it down.

There is a real trade-off here, and it is worth naming. Scanners genuinely catch a class of problems that upstream specs never will: a vulnerable transitive dependency, a subtly wrong crypto call, slopsquatted packages. Keep the scanner. This is not scanner-versus-spec. It is that the scanner is the second line, and right now most builders are running it as the only line, against code whose intended behavior was never defined. The first line is cheaper and you skipped it.

#Move security into the definition of done

The reframe is to stop treating security as a scan you bolt on at the end and start treating it as behavior you define at the beginning. The common holes map, almost one to one, onto acceptance criteria a non-engineer can write in plain language.

Consider the difference.

Vague: "Build me a dashboard where users can see their orders."

Specified: "Build an orders dashboard. Only authenticated users can access it. Each user sees only their own orders, never another user's, enforced at the database layer, not just hidden in the UI. Unauthenticated requests redirect to /login. No API keys or secrets appear in client-side code."

Same feature. The second version made four security decisions out loud: authentication is required, authorization is per-user, enforcement is server-side, secrets stay server-side. The agent now has no room to guess "everyone can see everything" because that is the fastest path to a working demo. And, just as importantly, you now have four concrete things to verify against, whether you can read the code or not. "Does an unauthenticated request redirect to /login?" is a question anyone can check by trying it. "Is the auth logic correct?" is not.

This is the whole game, and it echoes the best line we read all week, from a random Hacker News comment that has stuck with us:

You can make good software with agents, if you exhibit good judgement and put yourself in the path as a gate on quality.

Put yourself in the path. Not by reading every line, most vibe coders can't, but by defining the behavior the code must satisfy and then checking the result against it. Security is not a property of the code you can eyeball. It is a set of behaviors you can specify and verify. Missing auth is a criterion you did not write. A table without row-level security is a criterion you did not write. A leaked key is a criterion you did not write.

#Where this fits in the loop

This is exactly the gap BrainGrid is built to close. You describe the feature in plain language, and the Planning Agent turns it into a requirement with explicit acceptance criteria, including the security ones you would otherwise leave implicit: who is authenticated, who is authorized to see what, where enforcement happens, what data must never be exposed. Those criteria are not decoration. They are the standard the Builder Agent builds against and the checklist a feature is verified against before it counts as done. A build with an unprotected admin route does not quietly pass, because "admin routes require an admin role" is written down as a criterion, and a feature is not done until every criterion is verified with evidence.

That is the loop: Plan the behavior including its security posture, Build against it, Verify each criterion with evidence, Repeat. The security holes the scanners find live in the gap between "looks done" and "is done." Closing that gap with criteria you defined up front is what turns a demo that happens to work into software you can actually trust with real users and real data.

#What this means if you are building right now

If you are vibe-coding a real app in Lovable, Cursor, Claude Code, or Bolt this week, the practical implication is narrow and immediate. Before your next feature, write down the answers to four questions the agent is otherwise deciding for you: Who has to be logged in? Who is allowed to see or change which data? Where is that enforced, in the UI or in the database? And what secrets must never leave the server? Those four answers are your security acceptance criteria. They take five minutes and they close the majority of the holes that show up in every review of vibe-coded apps.

The uncomfortable truth underneath all of this is that the AI is not making your security decisions badly. It is making them silently, on your behalf, every time you leave them out of the prompt. The fix was never a better scanner. It was saying what secure means before the agent builds, so there is a standard to build toward and something concrete to check against when it is done. If you want the fuller version of this discipline, we wrote about how to vibe code the right way by planning first, and about why the oversight is the premium once the agent does the typing.

#FAQ

#Is vibe coding safe?

Vibe coding is as safe as the requirements you give it. The tool is not the risk. The risk is unspecified behavior: when you do not say who must be authenticated, who is authorized, and what data must stay private, the agent picks defaults that favor a fast demo over a secure app. Define those behaviors as acceptance criteria and the same tools produce far safer output. Safety comes from the spec, not the scanner.

#How dangerous is vibe coding?

The danger is concentrated in a small, repeatable set of holes, not spread randomly across everything the AI writes. Reviews of many vibe-coded apps keep finding the same four: missing authentication, no row-level security on the database, hardcoded secrets, and overly permissive defaults. Because the failures are predictable, they are preventable. Each one corresponds to a decision you can make explicitly up front instead of letting the agent make it silently.

#What are the most common vibe coding security risks?

The common ones are missing or weak authentication on protected routes, missing authorization so users can access other users' data, backend databases like Supabase deployed without row-level security policies, hardcoded API keys and secrets left in the codebase (often shipped to the client), and unaudited third-party packages pulled in by default. Every item on that list is a behavior you can specify before the build rather than a bug you hunt after it.

#Should I use a security scanner for vibe-coded apps?

Yes, as a second line of defense, not your only one. Scanners catch things upfront specs will not, like a vulnerable dependency or a subtly wrong crypto call. But a scanner reports a hole after it exists, in code you then have to understand to fix. Defining security behavior as acceptance criteria prevents most of the common holes from being generated in the first place. Use both: specify the behavior, then scan to catch what slipped through.

#How do I make a vibe-coded app secure without reading all the code?

Shift the work from reading code to defining and verifying behavior. Write down, in plain language, who must be authenticated, who is authorized to see or change which data, where that enforcement lives (server-side, not just hidden in the UI), and what secrets must never reach the client. Then verify each one by testing it: try an unauthenticated request, try to access another user's data, check the client bundle for keys. You do not need to read the auth code to confirm the auth behavior is correct.

BrainGrid is the AI Product Planner that turns your idea into a requirement with acceptance criteria, including the security ones, so your agent builds toward a standard instead of guessing. Try it at braingrid.ai.

About the Author

The BrainGrid team building tools to help developers ship better software with AI.

Want to discuss AI coding workflows or share your experiences? Find me on X or connect on LinkedIn.

Get Started

Ready to build without the back-and-forth?

Turn messy thoughts into engineering-grade prompts that coding agents can nail the first time.

Describe what you want to build