Is your vibe-coded app production-ready? A 15-point audit
C.E.O. Bee Engineering24 min read
A working prototype is not a production-ready app. Before you open an app written with AI tools to real users, audit the risks across fifteen items: secrets, authentication, authorization, input validation, the database, backups and restore, environments, error monitoring, dependencies, tests, performance, payments, personal data, cost and ownership. If any item on secrets, identity, access control, input validation, backups, payments or personal data has failed, do not go live. This article shows how to check each item and what counts as a pass.
What is vibe coding?
Vibe coding means describing what you want in plain language and accepting the code an AI model writes, often without reading it. Andrej Karpathy popularized the term in an X post on February 2, 2025 (Wikipedia), and Collins Dictionary named it its Word of the Year for 2025 (Collins). This article is not about the definition or the tools. It is about one question: is a prototype that works on screen ready for real users?
Vibe coding risks: the difference between a prototype and production
A prototype exists to show an idea. Production is where real people use real data and a mistake has a real price. AI has made the path from an idea to a working screen much shorter; taking that screen to production is still engineering work. The table below summarizes what changes when you go from prototype to production.
| Dimension | Prototype | Production |
|---|---|---|
| Users | You and a few friends | Strangers who have no patience for errors, and some who mean harm |
| Data | Sample or fake records | Real data whose loss or leak has consequences |
| Cost of a failure | You refresh the page | Lost orders, double charges, reputation damage, legal obligations |
| Security | "Nobody will try" | An address exposed to the internet is easy for automated scanners to find and probe |
| Backups | None, or "I'll rebuild it" | A backup you have proven you can restore |
| Monitoring | "Someone will tell me" | You see the error before the user does |
| Change | Any time, straight into the app | Controlled: tests, separate environments, rollback |
| Ownership | In the tool's account or a friend's | Code, data and accounts in your name, transferable |
The question is not new. The pre-launch checklist in Google's SRE book (Appendix E) walks through architecture, capacity, backup and disaster recovery, monitoring and security. The list below covers the same ground, ordered by the typical weak points of a small app generated with AI.
The 15-point production-readiness audit
Every item has the same four lines: why it matters, how to check, pass criterion and fix effort. Fix effort is a relative label, not a duration or a price: S is a single setting or a small change; M touches several files or screens and needs testing; L means restructuring something fundamental, such as the data model or the permission logic. An item that does not meet its pass criterion has failed; the rest of the article uses the two words in that sense.
The principle behind the audit is the one we wrote about in our engineering note Proposals, Not Actions: do not trust what the AI produced; build the control outside it, in the system. The commands are written to run in a macOS or Linux terminal (on Windows, use WSL or Git Bash); adapt file names to your own project.
1. Secrets: API keys, .env and leaks into the client bundle
Why it matters. AI tools can write a key straight into a source file in example code; the file goes to the repository, and from there to a public package or to the browser. Anything shipped to the browser is visible to everyone: in Vite, variables with the VITE_ prefix (docs), and in Next.js, variables with the NEXT_PUBLIC_ prefix (docs), are bundled into the client code at build time. The Vite documentation says these variables should not contain API keys.
How to check. In the repository history: git log --all --oneline -S"sk_live_" and git log --all --oneline -G"sk_(live|test)_[0-9A-Za-z]+" (they list the commits that added or removed a key; open a hit with git show). For very large repositories, or for other kinds of keys, a secret scanner such as gitleaks or trufflehog can help. In the built bundle: grep -rEn "sk_(live|test)_|AKIA[0-9A-Z]{16}|eyJ[A-Za-z0-9_-]{10,}\.eyJ" dist/ .next/static/. Long strings starting with eyJ are JWTs (Supabase keys are one); decode the middle part from base64: if it says "role":"service_role", a full-access key is in the bundle, while anon is expected to be public and is only as safe as item 3. In the browser, open the Network tab of the developer tools and read the requests.
Pass criterion. No live key in the history or the bundle. Every key that was ever exposed has been revoked and replaced: OWASP says exposed keys should be revoked immediately (OWASP). Deleting it from the file is not enough; the key stays in the repository history.
Fix effort. S (rotate the key).
2. Authentication and sessions
Why it matters. AI usually produces a login screen that works, but working is not the same as secure. How is the password stored, how is the session carried, can a password-reset link be used twice? OWASP Top 10:2025 lists "Authentication Failures" as its own category (A07).
How to check. Confirm that a ready-made identity service or a mature library is used where possible. If it was written by hand, check in the database that passwords are stored with a slow hashing algorithm (such as argon2 or bcrypt), not in plain text. Check where the session token lives: in Developer Tools > Application, open both Cookies and Local Storage (localStorage). If it is a cookie, look for the HttpOnly, Secure and SameSite flags. OWASP advises against keeping session identifiers in local storage because JavaScript can always read that data; cookies can mitigate the risk with the HttpOnly flag (OWASP). The client libraries of some ready-made identity services can write the session to browser storage (in the Supabase client, for example, the persistSession option saves the session to local storage when it is on, docs); if the token is there, protecting it from script injection is the job of item 4. Try to use a reset link twice. Send repeated wrong passwords to the login endpoint.
Pass criterion. Passwords are hashed; the session token is in an HttpOnly, Secure cookie (if it is in localStorage, the reason is written down and item 4 has passed); reset links are single-use and expire; repeated failures are throttled.
Fix effort. S–M (moving to a ready-made identity service is M).
3. Authorization: object level
Why it matters. Being logged in does not mean being allowed to see every record. "Broken Access Control" is first on OWASP Top 10:2025; its API counterpart is "Broken Object Level Authorization" (API1:2023). AI often adds the check "is the user logged in" and can skip the check "does this record belong to this user."
How to check. Use two accounts. Open a record as account A, then take the ID from the network request (/api/orders/123), replace it with the ID of a record that belongs to account B and send the request again. Call admin endpoints with a normal user's session. If the app talks to the database straight from the browser (as with Supabase), check whether row level security (RLS) is on: select schemaname, tablename, rowsecurity from pg_tables where schemaname = 'public'; and select * from pg_policies;. According to the Supabase documentation, a table without row level security is readable and writable by any role that has a grant on it. The same page says a view that exposes a table bypasses RLS by default; in Postgres 15 and above, setting security_invoker = true makes the view follow the policies of the underlying tables (docs). List the views with select schemaname, viewname from pg_views where schemaname = 'public';.
Pass criterion. B's data never comes back to A by any route (403 or 404); admin endpoints check the role on the server, on every request (OWASP); every table has row level security enabled and a policy defined, and there is no view that exposes a table, or it is defined with security_invoker (Supabase).
Fix effort. M; L if the data model has no "owner of the record" at all.
4. Input validation and injection
Why it matters. Every value that arrives from a form, an address bar or a file upload is untrusted. Injection is A05 on OWASP Top 10:2025. Generated code may build a query by concatenating strings or render user text into the page as HTML.
How to check. Search for concatenated queries: grep -rnE '(query|execute|raw)\(.*(\+|\$\{)' src/. Search for places that insert raw HTML: grep -rnE "dangerouslySetInnerHTML|innerHTML" src/. Type ' and <img src=x onerror=alert(1)> into forms and watch what happens. Confirm that the type, size and destination of uploaded files are checked on the server.
Pass criterion. All database access is parameterized or goes through a trusted ORM; user text is displayed as plain text, not as HTML (it is escaped); uploads have a type and size limit enforced on the server.
Fix effort. S–M.
5. Database: schema, constraints, indexes, migrations
Why it matters. In a prototype the data model is built the first way that comes to mind: no constraints, no indexes, a schema changed by hand in a console. Once production data is corrupted, repairing it is the most expensive repair there is.
How to check. Are there migration files in the repository, and can an empty database be built from them alone? Open a new, empty database and apply them. Look at foreign keys, uniqueness and NOT NULL constraints on the important tables (\d table_name in psql). Are the columns you filter on often indexed? Compare the production schema with the one in the repository: has anyone edited it by hand in a console? Are sample data or test users still in production?
Pass criterion. An empty database is built from the repository's migrations and the app starts; related records are protected by constraints; the production schema matches the repository.
Fix effort. M; L if the data model itself is wrong.
6. Backups and a restore test
Why it matters. A backup you have not restored is an assumption. The AWS Well-Architected guidance lists "assuming that a backup exists" among the common anti-patterns and recommends restoring from backups periodically (REL09-BP04).
How to check. Where is the backup, how often is it taken, and in whose account? Restore the latest backup into a new, empty database; check the row count and the most recent record in one table; note how long the restore took. If uploaded files (images, documents) live in a separate store, are they backed up too? Is automatic backup really included in your hosting plan?
Pass criterion. The date and duration of the last restore test are written down; the restored data is complete and readable; the backup lives somewhere separate from the account the app runs in.
Fix effort. S–M.
7. Environments and deployment
Why it matters. A prototype is often a single environment: a change made in the tool's editor goes straight to the live app and touches live data. The Twelve-Factor principle is that configuration is kept apart from code; the codebase should be ready to be open-sourced at any moment without exposing a single credential (12factor.net).
How to check. Are there separate databases and separate keys for development and production? Are preview builds connected to production data? Is deployment a single command or automatic, or a manual upload? Can you return to the previous version in one step? Try it once. Whose account holds the domain, the DNS records and the TLS certificate?
Pass criterion. A test change cannot touch production data; a rollback to the previous release has been tried and worked; configuration lives in environment variables.
Fix effort. M.
8. Error handling, logging and monitoring
Why it matters. OWASP Top 10:2025 has two related categories: "Security Logging and Alerting Failures" (A09) and "Mishandling of Exceptional Conditions" (A10). In a prototype an error either lands in the console or is shown to the user as a stack trace. The first blinds you; the second helps an attacker.
How to check. Break something on purpose: cut the database connection, send invalid input. What does the user see, and how do you find out? Is there an error-tracking service and an uptime check, and does the notification reach a person? Logs should not contain passwords, access tokens, keys, database connection strings or card data (OWASP).
Pass criterion. The user gets a clear message and you get the details; a critical error reaches a person as a notification; there are no secrets in the logs.
Fix effort. S–M.
9. Dependencies: unused, invented, vulnerable, licensed
Why it matters. AI-written code pulls in many packages; some are unused, some are old, some do not exist at all. A study of code-generating language models found package hallucination to be a persistent and systemic phenomenon (Spracklen et al., USENIX Security 2025); "Software Supply Chain Failures" is the third category on OWASP Top 10:2025 (A03).
How to check. npm audit --omit=dev checks production dependencies against known vulnerabilities; its exit code depends on the --audit-level setting: --audit-level=high exits with a non-zero code only for high and critical vulnerabilities (docs). The command is for JavaScript and TypeScript (npm) projects and by default needs a lock file; other ecosystems, such as Python, have their own equivalents, for example pip-audit. Check that each package really exists and that its publisher and age look reasonable: npm view <package> name version time.created maintainers. Remove what is unused. Is the lock file (package-lock.json) in the repository? Review the licenses for commercial use.
Pass criterion. No high or critical vulnerability in production dependencies, or a written, reasoned exception; every package verified; the lock file is committed.
Fix effort. S–M.
10. Tests: critical flows and meaningful tests
Why it matters. AI also writes tests, but sometimes they confirm what the app does rather than what it should do. The way to find out whether a test is worth anything is to try to break it.
How to check. List the critical flows: sign-up, login, payment, writing data. Does each have a test? Break a rule on purpose (double the price calculation, comment out a permission check): if no test fails, the tests are not doing their job. Do the tests run automatically on every change? For existing code without tests, "characterization tests" first record the behavior as it is (Feathers).
Pass criterion. The critical flows have tests that fail when the rule is broken, and they run automatically on every change.
Fix effort. M.
11. Performance and scale
Why it matters. A prototype is tried with ten records. With thousands, a list without pagination, or a screen that runs one query per row (N+1), locks up. The first bottleneck is often the database.
How to check. Open the list and search pages with hundreds and thousands of test records and measure them (the browser's Network tab is enough). Look at how many queries one request runs; if your database provider has a slow-query view, open it. Do list endpoints paginate? Are images resized? Do calls to outside services have timeouts?
Pass criterion. With a realistic data volume the main screens open within a target time you wrote down beforehand; lists are paginated; outbound calls have timeouts.
Fix effort. M.
12. Payments and sensitive flows
Why it matters. A notification (webhook) from a payment provider arrives asynchronously; it can be forged, it can arrive twice, it can arrive out of order. The Stripe documentation says to verify the signature (Stripe-Signature), not to touch the raw request body, to log processed event IDs so duplicates are skipped, and that event order is not guaranteed (webhooks). The principles are the same for other providers; the details are in the provider's own documentation.
How to check. Send an unsigned, forged request to the webhook endpoint: it should be rejected. Send the same event twice: only one order should result. Change the amount in a request body using the developer tools: the server should calculate the amount itself. Does an order become "paid" because of a verified notification, or because the user landed on a redirect page? Do retried create requests use an idempotency key (Stripe)?
Pass criterion. A forged, repeated or altered request creates no record; the amount is computed on the server; status changes only on a verified notification.
Fix effort. M; L if the payment flow was built wrongly from the start.
13. Personal data and privacy law
Why it matters. An AI-built app can collect personal data without you noticing and send it to third-party services (analytics, email, an AI provider). Check the data-protection law that applies to your users: for users in Türkiye that is the Personal Data Protection Law No. 6698 (KVKK), and other jurisdictions have their own rules, such as the GDPR in the EU. Under Article 12 of KVKK the data controller must take the technical and administrative measures needed to prevent unlawful processing of and access to personal data and to keep it safe; if data is obtained unlawfully by others, the controller must notify the data subject and the authority as soon as possible (mevzuat.gov.tr, in Turkish). Transfer abroad is regulated separately in Article 9. In November 2025 the Turkish authority also published a guide on generative AI and personal data (KVKK, in Turkish).
How to check. Build a data inventory: which personal data (name, email, phone, location, payment details), where is it stored, who can read it, which third parties receive it, how long is it kept, how is it deleted? Does user data go into prompts? Delete a test user and see what is left in the database, the logs and the backups. Does your privacy notice describe what you actually do?
Pass criterion. For every piece of personal data, its location, access, third parties and deletion route are written down; a deletion request can be fulfilled; you know what data goes to the AI provider. This item is not legal advice; ask a lawyer for a legal assessment.
Fix effort. M–L.
14. Cost and abuse
Why it matters. In an app that calls an AI model or another paid API straight from the browser, every visitor can grow your bill. OWASP's list for LLM applications calls this "Unbounded Consumption" (LLM10:2025); the API list has "Unrestricted Resource Consumption" (API4:2023) and "Unrestricted Access to Sensitive Business Flows" (API6:2023), which covers automated abuse of business flows (list).
How to check. Confirm that paid-service keys never reach the browser (item 1). Send a hundred requests in a row to one endpoint of your own app and count the response codes (replace the address with your own): for i in $(seq 100); do curl -s -o /dev/null -w "%{http_code}\n" https://example.com/api/endpoint; done | sort | uniq -c. At some point you should see 429. If the endpoint calls a paid service (an AI API, for example), try it with seq 10 first or run it in a test environment: a hundred requests can grow your own bill and get your IP address blocked. Is a monthly spending cap and a budget alert set in the provider console? Is there bot protection on the sign-up form?
Pass criterion. Repeated requests are throttled; a spending cap and alert are in place; paid keys exist only on the server.
Fix effort. S–M.
15. Ownership and maintainability
Why it matters. If the code sits inside an AI platform, the accounts sit under a friend's email and the domain is on a personal account, losing access one day can stop the product. If nobody can read the code, the first serious bug locks it up.
How to check. Fill in this list: code repository, hosting, domain and DNS, database, email and SMS, payment account, store accounts, AI provider account. Who owns each, who holds the admin account, is it tied to a company email? Can you export the code from the platform to a repository? Does the repository have a README that says how to install and run it, and does it work on a clean computer when followed step by step?
Pass criterion. All accounts are in the company's or the product owner's name; the code is exported and in a repository; an engineer can run the project on day one without the AI tool.
Fix effort. S–L.
If you are not an engineer: six items you can check yourself
These six items call for answers or evidence, not for reading code. If you cannot say "yes, here is the proof" to each question, that item has failed.
| Item | Question to ask | Evidence to request |
|---|---|---|
| 1. Secrets | Was a key ever shared, and if so, was it changed? | The last rotation date of the keys |
| 6. Backups | When was the last backup restored? | The date and duration of the restore |
| 7. Environments | Can a test change touch live data? | A separate test environment and a tried rollback |
| 13. Personal data | Which personal data, where, with whom? | The data inventory table |
| 14. Cost | Is there a spending cap? | A screenshot from the provider's dashboard |
| 15. Ownership | Who is the admin of every account? | The account list and the admin emails |
Verdict: red, yellow, green
The thresholds below are this article's recommendation, not a standard. If your app has no such feature (for example, payments), that item counts as "not applicable."
| Color | Condition | What to do |
|---|---|---|
| Red | One of items 1, 2, 3, 4, 6, 12 or 13 has failed | Do not go live. User data or money is involved; fix that item first. A red item cannot be waived with a reasoned exception (apart from items that are "not applicable"). |
| Yellow | The red items passed; one or more of the others failed | Launch to a limited, friendly group of users; write a fix plan with an owner and a date for every remaining item. |
| Green | All items passed; on items that are not red, a reasoned exception may be written down | Go live; repeat the audit after every large change. |
Fixing it yourself, and when to call an engineer
Some items close with a little learning and a few settings: rotating keys, switching backups on and testing a restore, setting a spending cap, moving accounts into your own name, removing unused packages.
Other items touch the structure and are an engineer's job: the permission model, the payment flow, the data model and migrations, meaningful tests, personal-data flows. For the personal-data item you may also need a lawyer next to the engineer.
The honest answer to "rewrite or fix?" depends on which layer failed. If authentication, authorization or the data model was built wrongly, that layer has to be rebuilt. The screens, the flow and the feedback you collected from first users are often the truly valuable part of a prototype. Throwing the old code away entirely and keeping it entirely are both rules of thumb, not laws; for the two familiar extremes see Joel Spolsky and Martin Fowler. The decision should be written down with its reasoning.
Next step
If you have a prototype, our MVP development page explains where we start: we look at what you have together and decide, with the reasoning in writing, whether to build on it or start again. If your app needs business rules specific to you, the custom software development page is the place to continue; if you are adding an AI feature, see AI integration; if it is a phone app, see mobile app development. When you are ready, describe your project.
Frequently asked questions
Is vibe coding safe?
No tool is safe or unsafe on its own; what makes it safe is checking its output. Vibe coding risks sit in the unchecked output: AI can produce a working app, but it does not spontaneously ask the questions you did not ask about permissions, backups, payments and personal data. The 15 items in this article put those questions in order.
If the code came from AI, who is responsible?
This is a legal question; the answer may depend on the contracts (the tool's terms of use, any contract with a developer) and on the facts. For personal data under Turkish law, Article 12 of Law No. 6698 makes taking the necessary measures the data controller's obligation. For a specific situation, ask a lawyer.
Can an app built with Lovable, Bolt or Cursor go live?
It can. These tools produce output; whether the output is ready for production is a separate question, and the audit in this article is independent of the tool. If you use the hosting the tool offers, find out in advance whether you can export the code and the data (item 15).
How long does it take to make an app production-ready?
It depends on the scope, so we give no figure. What counts most is which items failed: rotating a key and rebuilding the permission model are not the same size of job. Running the audit first and labeling each failed item S, M or L is the first step toward a realistic schedule.
Rewrite or fix?
It depends on the layer that failed. Screens and flow can usually be kept; if authentication, authorization or the data model were built wrongly, those layers are rebuilt. Which is right should be decided after the audit, with the reasoning written down.
Sources
The pages below were opened and read on October 24, 2026. Pages that carry fees, thresholds or version numbers may change over time.
- Vibe coding, Wikipedia · Collins Word of the Year 2025
- OWASP Top 10:2025 · OWASP API Security Top 10 2023 · OWASP Top 10 for LLM Applications 2025
- OWASP Cheat Sheet Series: Secrets Management, Authorization, HTML5 Security, Logging
- Vite: environment variables · Next.js: environment variables · The Twelve-Factor App: Config
- Supabase: Row Level Security · Supabase: JavaScript client · Stripe: webhooks · Stripe: idempotent requests
- npm audit · Spracklen et al., package hallucinations (arXiv) · Feathers, characterization testing
- AWS Well-Architected REL09-BP04 · Google SRE book, Appendix E
- Law No. 6698 (KVKK), mevzuat.gov.tr, in Turkish · KVKK guide on generative AI and personal data, in Turkish
- Joel Spolsky, Things You Should Never Do, Part I · Martin Fowler, Strangler Fig
This article is general information; it does not replace a legal or security review.
— Related serviceMVP Development