DEVSECOPS
Vibe coding security: why "fix the vulnerabilities" isn't enough
I compared two approaches across 10 projects: asking the model to scan and fix at the end, or building with security md files from day one. Scan-and-fix falls short.
In one sentence
Scan-and-fix closed 55% of findings, but 73% of the authorization and business logic flaws survived; projects built with a security md from the start had 81% fewer findings.
Everyone who ships a project with vibe coding ends up typing the same prompt at some point: "Scan the whole project and fix the security vulnerabilities." I've typed it too. The model edits a few files, prints a summary like "12 vulnerabilities fixed", and you relax.
I wanted to know how much of that relief is earned, because it didn't match what I see on the pentest side. So I ran a small study. Across 10 different projects I compared two approaches: finishing the work and scanning afterwards, and building with security md files from the very first prompt.
Here is the result up front: telling the model to "fix the vulnerabilities" isn't enough. The rest of this post explains why, and what to do instead.
What I tested and how
I picked 10 projects. Each one uses a different language and stack, and each is the size of something you could build with vibe coding: a login screen, a few roles, a database, and one or two sensitive flows such as file upload or payment.
I looked at every project in three states:
- Raw. Built with feature prompts only, without a single word about security.
- Scan-and-fix. The raw version plus one prompt: "Scan the whole project and fix the security vulnerabilities you find."
- Security md. The same project, rebuilt from scratch with the same feature prompts. The only difference was a
SECURITY.mdin the repo, and aCLAUDE.mdpointing to it, before the first prompt.
I tested all three the same way: a fixed checklist I derived from OWASP ASVS, static analysis, and the usual manual web and API testing. When the same flaw appeared on several endpoints I counted each one, because to an attacker each is a separate door.
What the numbers say
| # | Project | Stack | Raw | Scan-and-fix | Security md |
|---|---|---|---|---|---|
| 1 | SaaS admin panel | Next.js + Prisma | 24 | 10 | 4 |
| 2 | Appointment API | Django + DRF | 19 | 8 | 3 |
| 3 | E-commerce | Laravel | 27 | 13 | 6 |
| 4 | Chat backend | Express + MongoDB | 23 | 11 | 5 |
| 5 | File sharing service | FastAPI + PostgreSQL | 21 | 9 | 4 |
| 6 | Invoice service | Spring Boot | 17 | 8 | 3 |
| 7 | URL shortener | Go + Gin | 14 | 6 | 2 |
| 8 | CRM | ASP.NET Core | 20 | 9 | 4 |
| 9 | Mobile app | React Native + Supabase | 26 | 13 | 6 |
| 10 | Forum | Rails | 23 | 10 | 4 |
| Total | 214 | 97 | 41 |
The raw versions had 214 findings in total, 61 of them high or critical. After scan-and-fix, 97 remained, so about 55% were closed. The versions built with a security md had 41 findings in total, 81% fewer than the raw ones.
The gap is wider for high and critical findings: 61 raw, 27 after scan-and-fix, 9 with a security md.
At first glance 55% doesn't look bad. The real story shows up when you look at which kinds of findings were closed and which stayed.
What scan-and-fix closes and what it misses
I split the findings into two groups.
Pattern flaws: SQL injection, XSS, hardcoded secrets, weak hashing, missing security headers, outdated dependencies. What they have in common is that you can see them in the code. The wrong line is sitting right there.
Design flaws: missing authorization, IDOR, tenant isolation, rate limiting, session management, business logic. Here no line is wrong. A check that should exist was never written.
| Group | Raw | After scan-and-fix | Closed |
|---|---|---|---|
| Pattern flaws | 118 | 14 | 88% |
| Design flaws | 96 | 70 | 27% |
| New findings introduced by the fixes | - | 13 | - |
The model is good at pattern flaws; it closed 104 of 118. With design flaws, 70 of 96 stayed in place. In the group that hurts the most, roughly three out of four findings survived scan-and-fix.
Then there are the 13 new findings, which the fixes themselves introduced. In one project the model added input validation and put a stack trace in the error message. In another it added the authorization check to one endpoint, forgot the export endpoint that returns the same data, and still wrote "authorization added" in its summary.
Out of curiosity I gave the same prompt a second time. Across the 10 projects it closed 9 more findings. A third round changed almost nothing. So the problem is in the prompt itself; asking for the same thing again doesn't change the outcome.
Why it isn't enough
I explained it to myself in four points.
A scanner sees the code that was written. It can't see the code that wasn't. The line findById(req.params.id) doesn't look wrong on its own. What's wrong is that the line asking who owns that record was never written. Catching that requires knowing the answer to "whose data is this?", and that answer lives in your head, not in the code.
The fix stays local. The model patches the spot it found. If the same flaw sits in three other endpoints and those files aren't in context at that moment, they stay as they are. In the Laravel project the IDOR on the order detail page was closed, and the same IDOR on the invoice PDF stayed.
The design decisions have already been made. If tenant separation wasn't part of the design from the start, adding it later means changing every query. A "fix the vulnerabilities" prompt doesn't take on a job that size. The model settles for the smallest change, and that is usually a patch.
The next feature gets written the old way. For me this is the most important one. You scanned, you fixed, and the next day you asked for a new endpoint. When the model writes that endpoint, it knows nothing about yesterday's scan. The rule isn't written down anywhere, so the same flaw comes back.
What a security md changes
A security md is a short rules file that sits in the model's context every time it generates code. Scan-and-fix happens once; the md is always there. That is where the difference comes from.
A simplified version of the file I used looks like this:
# SECURITY.md
## Data ownership
- Every table carries an `owner_id` or `tenant_id`.
- Every read and write query filters on that column. No exceptions.
## Authentication and authorization
- Default: every endpoint requires login. Public ones are listed explicitly.
- Role checks live in one middleware, not inside handlers.
- Never trust `user_id`, `role` or `price` fields sent by the client.
## Input and output
- All queries are parameterized. No SQL built by string concatenation.
- Error messages never return a stack trace, a query or a file path.
## Secrets and dependencies
- Secrets are read from environment variables only.
- Before adding a package, check that it actually exists.
## For every new endpoint
- Write tests for an unauthorized user and for another user's record.
- Does it need a rate limit? If not, say why in a comment.The file doesn't need to be long. In my runs, files longer than 60 to 80 lines didn't give better results; the model started skipping items in long lists. The OpenSSF guide says the same: keep instructions short and concrete. It also notes that persona prompts along the lines of "act as a security expert" don't help, and in some studies gave worse results.
As I expected, the md made the biggest difference on design flaws, which dropped from 96 to 29. A rule like "every query filters on owner_id" makes the model write that missing line every time. The gap that scan-and-fix can't find never opens.
How Claude handles this
In Claude Code no single feature does the job. There are several layers on top of each other, and it helps to know what each one is for.
CLAUDE.md. This file in the project root is loaded automatically in every session. You can write your security rules in it or reference SECURITY.md from it. The third state in my study was exactly this.
The security-guidance plugin. It's Anthropic's official plugin, and to me the most interesting piece here. It checks the code Claude itself writes at three points: a fast pattern check on every file edit (things like eval(, innerHTML, pickle), a review of the changed diff by a separate Claude at the end of every turn, and a deeper review on every commit that also reads the surrounding code. The reviewer isn't the instance that wrote the code; it's a separate call that starts with a clean context. You can add your own rules in .claude/claude-security-guidance.md.
/plugin install security-guidance@claude-plugins-official/security-review. Runs a security pass over the changes on your branch whenever you ask for it. There is also a GitHub Action that does the same on pull requests automatically.
Permissions and sandbox. These limit the agent itself, not the code it writes: which commands it can run without asking, which directories it can write to, and whether it can reach the network.
I should be straight about the limits. Anthropic's own documentation says md rules are guidance, not guardrails. The plugin doesn't block any write or commit; it hands the finding back to Claude, and the review model can miss things. If you need hard enforcement, you have to do it with a hook or a CI check.
There is another side to this as well. Because rules files are loaded automatically and treated as trusted, they are an attack surface themselves. The Cloud Security Alliance note from October 2026 and the OWASP cheat sheet give the same warning: treat changes to files like CLAUDE.md and AGENTS.md as seriously as changes to your CI config, and don't merge them without review.
Using both together
Finally I added a review layer on top of the 10 projects built with a security md: the security-guidance plugin on, and /security-review after every feature. Total findings went from 41 to 17, and high-severity findings from 9 to 2.
| State | Total findings | High / critical |
|---|---|---|
| Raw | 214 | 61 |
| Scan-and-fix | 97 | 27 |
| Security md | 41 | 9 |
| Security md + review | 17 | 2 |
My takeaway is that scanning is a fine idea that turns weak when it's the only thing you do and you do it last. The md makes the flaw harder to create, and the review catches what slips through anyway. The order matters.
Almost all of the remaining 17 findings were about business logic. A coupon that can be used twice, a cancelled order that still ships. Neither the md nor the review caught these, because only the person who knows the business knows what correct looks like. That part is still ours.
What to do tomorrow morning
- Put a short
SECURITY.mdin the repo before the first prompt and reference it fromCLAUDE.md. Put the data ownership rule at the top. - Write concrete rules. "Write secure code" does nothing; "every query filters on
tenant_id" works. - Install the security-guidance plugin and run
/security-reviewafter every feature. - For every new endpoint, ask the model for a test that proves "I can't reach another user's record". The test is your evidence that the rule was applied.
- Make secret scanning and dependency audits blocking in CI. Don't leave them to the md.
- Review every change to your rules files.
- If you already have a project, start with scan-and-fix but don't stop there. Turn the findings into rules and write them into the md so the same flaw doesn't return.
- Test the business logic yourself: negative amounts, sending the same request twice, skipping a step. Nobody thinks about these for you.
Useful resources and md files
- OWASP Secure Coding with AI Cheat Sheet: AI-specific risks such as hallucinated dependencies, prompt injection, the security of rules files, and deleted tests.
- OWASP ASVS: a checklist you can derive your own md items from. My test list came from here.
- OpenSSF: Security-Focused Guide for AI Code Assistant Instructions: what belongs in a rules file and what doesn't. It also summarizes the academic work behind the advice.
- Claude Code: security-guidance plugin: installation, the three review layers, and the
claude-security-guidance.mdformat. - Claude Code: Security: permissions, sandboxing and prompt injection protections.
- anthropics/claude-code-security-review: the GitHub Action that runs an automatic security review on pull requests.
- Wiz: secure-rules-files: ready-made starter rules files for Django, Flask, React, Node.js, Spring and ASP.NET Core, in
CLAUDE.md,AGENTS.mdand Cursor formats. They were generated with AI themselves, so read them and trim them for your project. - Cloudflare: Engineering standards enforcement: how Cloudflare writes its engineering standards as RFCs and has agents read them. The same idea at company scale. If you build on Workers, also look at the cloudflare/skills repo.
- CSA: Instruction-File Poisoning: a note on why rules files are an attack surface.
Sources: OWASP Secure Coding with AI Cheat Sheet · OpenSSF: Security-Focused Guide for AI Code Assistant Instructions · Claude Code: Security · Claude Code: security-guidance plugin · anthropics/claude-code-security-review · Cloudflare: How Cloudflare enforces engineering standards using AI · Wiz: secure-rules-files · CSA: Instruction-File Poisoning of AI Coding Agents