AI Code Rescue: What to Actually Do with a Vibe-Coded Prototype
Vibe-coded prototypes fail in a specific, consistent pattern. Here is the audit checklist and the remediation order for turning one into something safe to ship.
Vibe coding is a real productivity unlock. You describe what you want, an LLM writes it, you iterate in natural language, and in hours you have something that runs. The demo works. You can show it to investors or to your team. Then you try to ship it to real users — and the wheels come off.
The code your prototype depends on is not bad in the way that code written under deadline pressure by a junior developer is bad. It is bad in a specific, consistent pattern: coherent at the surface and hollow underneath. The happy path works. Everything else — auth edge cases, input from outside the test fixture, concurrent requests, failures in upstream services — is either untested or silently wrong. This is not the AI's fault. It is the natural output of a system that optimises for "this runs and matches the intent I described" rather than "this handles every way a real user could interact with it."
This post covers the audit, the prioritisation, and the remediation. What to look for, in what order, and what done actually means.
The Shape of a Vibe-Coded Prototype
Before you can fix it, it helps to understand the pattern. Vibe-coded prototypes tend to share the same structural characteristics regardless of the domain or the LLM used to write them:
- A single happy path that is solid and well-exercised, because that is what was demonstrated and iterated on during development.
- Authentication that works for the developer's own account and breaks down for edge cases: concurrent sessions, expired tokens, users with no data, users with more data than the test fixture.
- Input from the user that is trusted directly — used in database queries, in file paths, in log messages — because validation was not in scope for the demo.
- No meaningful error handling below the happy path. Errors are caught and either silently swallowed or returned directly to the caller, sometimes with a full stack trace attached.
- Secrets in environment variables that are correct locally, undocumented for anyone else, and in at least one case committed to git history at some point.
- Tests, if they exist, that were generated alongside the code and test the happy path only — which means they pass confidently even as the underlying issues remain.
None of this is catastrophic in isolation. The problem is the combination, and the fact that all of it is invisible until a real user exercises it.
The Audit: What to Look At First
Before fixing anything, understand what you have. A remediation that starts without a complete picture will fix the visible problems and miss the ones buried two layers down. Run this audit in priority order, because the priority order is the risk order.
1. Authentication and Authorisation
The most common vibe-coded auth bug is not the absence of authentication — it is that authentication happens on some routes and not others, or that authentication checks are present but the user ID from the session is not used to scope the database queries that follow. A user logs in as themselves and reads another user's data. This is an authorisation failure, and it is remarkably easy to introduce when the demo always used a single test account.
Check the following, explicitly, on every route:
- Is there any endpoint that returns data without first confirming the identity of the caller?
- Does every database read that returns user-specific data filter by the authenticated user's ID — in the query itself, not just in the application logic that processes the result?
- Is the session or JWT validated on every protected request, or only on the routes that were explicitly tested?
- Are admin or elevated-privilege routes gated separately from user routes, or does the same auth middleware cover both?
This audit should be done by reading the code, not by testing it. Test coverage of auth bugs is unreliable because the tests were written to pass, not to find failure modes.
2. Input Handling
AI-generated code frequently concatenates user input directly into queries, filenames, or shell commands. This is the simplest way to make the demo work, and it introduces SQL injection, path traversal, and command injection vulnerabilities that are trivial to exploit and invisible during happy-path testing.
Search the codebase explicitly for:
- Any user-supplied value used in a database query without parameterisation or a prepared statement. String interpolation into a SQL query is not safe regardless of what the input looks like in testing.
- Any user-supplied string used to construct a file path. The string
../../../etc/passwdis a valid filename on most systems. - Any input passed to
exec,subprocess,eval, or equivalent functions. These are rarely necessary and almost always avoidable. - Any HTML or rich-text content rendered from user input without sanitisation, which creates XSS surfaces in browser contexts.
3. Error Handling and Information Leakage
Stack traces in HTTP responses are not just embarrassing — they reveal your framework version, your file structure, your dependencies, and occasionally your environment variables. AI-generated error handling tends toward try/catch blocks that log and rethrow, which means the full exception, including stack trace and sometimes local variable state, ends up in the response body.
Check whether your API returns stack traces or internal error messages to unauthenticated callers. Check whether your logs contain tokens, user data, or secrets that you would not want in a breach disclosure. A log line that records a full request object, including authorization headers, is a common and avoidable mistake.
4. Secrets and Configuration
Hardcoded secrets — API keys in source files, connection strings in config — are common. Environment variable files committed to version control are common. Keys that were meant to be per-environment being shared across environments are common.
Run git log --all --full-history -- '*.env' — if any .env file was ever committed, the secrets it contained are in git history regardless of whether the file was later deleted. Search the codebase for API key and token patterns: long alphanumeric strings, strings that start with known vendor prefixes. Check whether your .gitignore correctly excludes all secret-carrying files.
The Remediation Order
The order you fix things matters. Fixing cosmetic issues before security issues is a common mistake when the cosmetic issues are more visible.
First: rotate any secrets that were ever at risk. Changing the code is not sufficient if a secret is in git history or was ever exposed. Rotate first, then fix the code that was using the old credentials. Rotating after the fix leaves a window during which the compromised credential was still valid.
Second: fix authorisation before anything else. Auth bugs can expose every user's data, and they tend to be structural — the fix requires changing the data access pattern across multiple routes, not patching a single function.
Third: fix input handling. Parameterise every database query. Replace filename construction from user input with allowlists. Remove any exec/eval call that touches user-supplied strings.
Fourth: harden error handling. Catch exceptions at the boundary, log internally, return a generic error response to the caller. No stack traces, no internal state, no hints about which specific check failed.
Fifth: document the deployment. The environment variables that exist only in your local shell history are a production incident for whoever deploys this next. Write a deployment checklist that enumerates every external credential, which environment it belongs to, where it should be stored, and how to rotate it.
Adding Structure That Holds
Beyond fixing the immediate issues, a vibe-coded prototype needs structure that allows other people to work on it and that prevents the same classes of bugs from recurring.
Write a test for each thing that was broken and is now fixed. The point is not coverage as a metric — it is a regression gate. A test that confirms the authorisation fix stays fixed is worth more than ten tests that exercise the happy path from a different angle.
Add a CI run that executes those tests on every commit. The goal is that the issues you spent time finding and fixing cannot silently return. A CI gate that runs in two minutes is sufficient. A comprehensive test suite that takes thirty minutes and never gets run is not.
Add a linter or static analysis pass for the vulnerability classes you found. If you found SQL injection, configure a rule that flags string interpolation in queries. If you found hardcoded secrets, add a secret scanning step. These tools are not comprehensive, but they catch regressions that a developer in a hurry would miss.
What Done Looks Like
A hardened vibe-coded prototype is not a different product. It is the same product, working the same way, with the specific things that would hurt a real user fixed and the structure in place to stop them coming back.
Done means: every route that returns data scopes it to the authenticated user. Every user-supplied value that touches a database goes through a parameterised query. Error responses tell the caller something went wrong without revealing why. Secrets are out of the repository and into a documented configuration system. There is a test for each thing that was broken, and CI runs it.
The prototype that got you to the demo was the right tool for that job. It is not the right tool for handling real users' data. The gap between those two things is specific and closable — and the cost of closing it before you ship is a fraction of the cost of closing it after something goes wrong.
When to Do This
Before you give any real user access to the product. The moment "me and my team testing this" becomes "people whose data I am responsible for are using this," every unfixed authorisation bug and every un-parameterised query becomes your liability.
If you are raising a round and the due diligence process includes a technical review, do it before the review. Technical reviewers who find auth issues and raw SQL string concatenation in the same session will flag both, and the combination is worse than either individually.
The prototyping phase of a project is the right time to move fast. The moment you decide it is worth shipping, it is the right time to close the gap.
Ostwind Labs builds production-grade AI systems: MCP servers, guardrailed agentic workflows, and RAG pipelines that hold up past the demo.
Start a project