Vibe Coding Gets You 80%. The Last 20% Is Security
Vibe Coding Gets You 80%. The Last 20% Is Security
Vibe Coding Gets You 80%. The Last 20% Is Security
Vibe coding gets you eighty percent. The last twenty is the whole job.
That final part is where the product meets real users, real money, and people who do not behave like your test prompt.
I have shipped what AI wrote. I have also fixed what it wrote.
The code often looks finished because the happy path works. A user clicks the button, the API responds, and the database updates. That is enough for a demo. It is not enough for production.
What the research found
A 2025 study by Maximilian Schreiber and Pascal Tippe examined code in public GitHub repositories that developers had explicitly attributed to ChatGPT, GitHub Copilot, Amazon CodeWhisperer, or Tabnine.
The researchers used GitHub CodeQL to scan the code for weaknesses mapped to the Common Weakness Enumeration system, better known as CWE.
Their reported pipeline included 7,703 files before a final attribution filter. After that filter, 7,117 files were analyzable for the vulnerability result. Of those files:
- 87.9% had no identifiable CWE-mapped vulnerability.
- 12.1% contained at least one CWE-mapped vulnerability.
- The researchers found 4,241 CWE instances.
- Those findings covered 77 different weakness types.
Twelve percent may sound small until that code handles authentication, payments, customer records, or a public API.
The paper also found serious weakness categories in the dataset, including SQL injection, command injection, code injection, and hard-coded credentials. These were ranked by the average severity of related public vulnerabilities, not by how often every developer will encounter them.
There are limits to the study. It only captured files with explicit AI attribution. CodeQL is static analysis, so it cannot catch every runtime bug or business logic failure. The dataset was also heavily weighted toward ChatGPT-attributed files. The result is useful evidence, but it is not a universal failure rate for every model or every codebase.
The four failures I check in real projects
The next four items come from my engineering experience. They are not the paper's ranking of its most common findings.
They keep appearing because a prompt usually asks AI to make a feature work. It rarely defines how that feature should behave under abuse, partial failure, leaked credentials, or repeated requests.
1. No rate limit
Suppose AI builds an endpoint that generates a report, sends an email, or calls a paid model. The endpoint works perfectly during testing.
Then one user sends thousands of requests.
Without a limit, that user can consume your compute, fill your queue, trigger paid services, or make the API unavailable to everyone else. OWASP classifies this as unrestricted resource consumption.
Add limits at more than one level:
- Requests per IP address.
- Requests per authenticated user.
- Requests per workspace or customer account.
- Expensive operations per hour or day.
- Maximum payload, upload, batch, and response sizes.
- Timeouts for database and external API calls.
- Spending caps for paid providers.
A global rate limit is rarely enough. A login endpoint, file upload, AI generation request, and account lookup do not have the same cost or abuse risk.
Start by writing a table for every public endpoint:
Endpoint
Limit
Window
Cost cap
Action when exceeded
POST /login
5 attempts
15 minutes
None
Return 429 and delay retries
POST /generate
20 requests
1 hour
$10 per workspace
Stop new jobs and alert
POST /upload
10 files
1 hour
25 MB per file
Reject before processing
Tune the numbers to your product. The important part is deciding before launch.
2. Error handling that leaks or hides the problem
AI-generated code often handles only two states: success and crash.
A raw crash can return stack traces, database details, internal paths, or third-party responses to the browser. A swallowed error creates the opposite problem. The user sees nothing, and your team has no record of what failed.
Use two separate error messages:
- The user receives a safe, short message with a request ID.
- Your private logs receive the technical details needed to investigate.
For example, the customer can see:
We could not complete this request. Reference: req_8f31c2
Your private log can record:
request_id=req_8f31c2
route=/payments
user_id=usr_482
provider_status=timeout
duration_ms=10002
Do not put passwords, access tokens, full payment details, or sensitive customer data in either message.
Then test the failures you expect to happen:
- The database is unavailable.
- A third-party service times out.
- A required field is missing.
- The user lacks permission.
- The job fails halfway through.
- Logging itself is unavailable.
If you only test a successful request, you have only tested the demo.
3. Secrets in the front end
Anything sent to a browser or mobile client should be treated as visible to the user. Hiding a key in JavaScript, minifying the bundle, or placing it in a front-end environment variable does not make it private.
The safe pattern is simple:
Browser
-> Your server
-> Secret manager
-> External provider
The browser sends an authenticated request to your server. Your server checks the user's permission, reads the secret at runtime, and calls the provider. The secret never enters the browser bundle.
Before release:
- Search the repository for API key patterns and private tokens.
- Scan the built front-end files, not only the source folder.
- Keep production secrets in a managed secret store.
- Give each service its own narrowly scoped credential.
- Rotate any credential that has entered Git history.
- Block commits that contain detected secrets.
Deleting a secret from the latest file is not enough if it remains in commit history. Treat an exposed secret as compromised and rotate it.
4. No idempotency for actions with consequences
A customer clicks "Pay" once. The network times out. The browser retries. Your server receives the same request twice.
If the operation is not idempotent, the customer may be charged twice.
The same risk appears in order creation, subscription changes, emails, refunds, bookings, and webhook processing.
For each high-impact operation:
- Require a unique idempotency key from the caller.
- Store the key with the user, operation, input fingerprint, and result.
- Create a database uniqueness rule for that key and operation.
- If the same key returns, send the saved result instead of repeating the action.
- Reject the key if it is reused with different input.
- Set a retention period that matches the business risk.
Do not rely on an in-memory variable. It disappears during a restart and does not protect you when several server instances handle requests at the same time.
The database or transaction system needs to enforce uniqueness.
A production workflow for AI-generated code
The fix is not to stop using AI. The fix is to change what "done" means.
Step 1: Define the security rules before prompting
Add the missing requirements to the task:
Build this endpoint with authentication, object-level authorization,
input validation, a per-user rate limit, safe error responses,
structured logging, an idempotency key, and tests for duplicate requests.
Do not expose secrets to the client.
This will improve the first draft. It does not replace review.
Step 2: Mark every trust boundary
List each place where data crosses from one system or user to another:
- Browser to API.
- API to database.
- API to third-party provider.
- Webhook sender to your application.
- Background worker to an action queue.
At every boundary, ask four questions:
- Who is calling?
- Are they allowed to do this to this specific record?
- Is the input valid and limited?
- What happens if the request is repeated?
Step 3: Run automated checks
Your pipeline should fail when it finds a serious issue. At minimum, run:
- Static security analysis for the languages you use.
- Dependency and package vulnerability scanning.
- Secret scanning across source and build output.
- Unit tests for validation and authorization.
- Integration tests for timeouts and partial failures.
- Duplicate-request tests for high-impact actions.
Reports that nobody must fix are decoration. Set a severity threshold that blocks release.
Step 4: Review the business logic manually
Static analysis can find known code patterns. It may not understand that one customer can edit another customer's invoice or that a refund can run twice.
For each route that reads or changes customer data, review:
- Authentication.
- Record-level authorization.
- Tenant separation.
- Side effects.
- Retry behavior.
- Audit history.
- Recovery after a partial failure.
This review should focus on what the code is allowed to do, not only whether it compiles.
Step 5: Release with limits and visibility
Start with a small group or a percentage of traffic. Watch errors, latency, request volume, provider spending, and duplicate operations.
Set alerts before the release. Decide who receives them and what threshold should stop the feature.
A kill switch should disable the risky action without taking the whole application offline.
The release checklist
Before shipping AI-generated code, confirm:
- [ ] Every endpoint authenticates the caller where required.
- [ ] Each request checks access to the specific record or tenant.
- [ ] Inputs have type, format, size, and range limits.
- [ ] Public and expensive endpoints have suitable rate limits.
- [ ] External calls have timeouts and controlled retries.
- [ ] User errors reveal no internal details.
- [ ] Private logs include a request ID and enough diagnostic context.
- [ ] No production secret appears in source, Git history, or built front-end files.
- [ ] High-impact actions use persistent idempotency protection.
- [ ] Static analysis, dependency scanning, and secret scanning pass.
- [ ] Failure paths and duplicate requests have automated tests.
- [ ] Production has alerts, spending caps, and a kill switch.
- [ ] A person reviewed the business logic and permissions.
The part AI did not know you needed
None of these failures proves the model is stupid.
You asked it to make the feature work. It made the feature work. Nobody asked it to make the feature safe under abuse, retries, leaked credentials, or partial failure.
That is the last twenty percent.
It is also the part your customers are trusting you to finish.
Sources
Stay ahead of the curve
Join my private newsletter for exclusive insights, tools, and thoughts straight to your inbox. No spam, just value.