Vibe coding security checklist: 30 checks before your AI-built app goes live
A vibe coding security checklist for apps built with Lovable, Bolt, Cursor or ChatGPT: exposed keys, database rules, auth, payments and risks AI code misses.
This vibe coding security checklist covers the 30 checks I would run on any app built with Lovable, Bolt, Cursor, Replit or ChatGPT before real users and real money touch it. The biggest risks are almost never clever attacks. They are open doors: secret keys sitting in the browser, database tables anyone can read, pages that hide data behind a login screen but hand it to anyone who asks the API directly, and payments marked as paid because someone opened a success page. Most of these you can check yourself in an afternoon, without writing code, and the list below tells you how.
Quick answer. Before launch, check ten areas: secrets and keys, database access rules, authentication, authorisation, input validation, payments and webhooks, file uploads, dependencies, logging and errors, and backups and deploys. If you only have an hour, do these five first: search your live site's JavaScript for secret keys, confirm row level security is on for every Supabase table, log in as two different users and try to open each other's records, try to open your payment success page without paying, and confirm you have a backup you have actually restored. Asking the AI to review its own code helps, but it is not a substitute for testing the running app.
Why AI-built apps share the same security risks
AI coding tools write code that works for the person typing the prompt. You are one user, on a good connection, clicking buttons in the expected order. Security problems live in everything else: a second user, a curious user who edits a request, a retry, a file that is not what it claims to be. The tool was never asked about those cases, so it did not handle them.
There is a pattern to the vibe coding security risks I see. Broken access control, meaning users can reach data or actions they should not, sits at the top of the OWASP Top 10 (opens in a new tab), the standard list of the most serious web application risks. AI-built apps make it more likely because so many of them let the browser talk to the database directly. That design is safe only when the database rules are right, and the rules are exactly what a prompt like "add a dashboard" does not mention.
Two more things make vibe coded app security harder than it looks:
- Security settings often live outside the code. Row level security, storage bucket settings and API key restrictions are set in dashboards. An AI that reads your code cannot see them, so a code review can look clean while the database is wide open.
- Fixes get undone. A later prompt can quietly rewrite a policy or move a key back into the frontend while fixing something unrelated. A check you passed last month is not a check you pass today.
How to use this checklist
Work through it on your live app, or on a copy that uses the same settings. You will need two test accounts that are not admins, one admin account, and a browser with developer tools (right-click, Inspect, in Chrome, Edge or Firefox). Tick an item only when you have actually tried it, not when the AI says it is done. Anything that fails in the first four sections is urgent: fix it, or take the feature offline, before launch.
1. Secrets and keys
- No secret keys in the frontend. Open your live site, open developer tools, go to the Sources tab (Debugger in Firefox) and search across all files for
secret,service_role,sk_live,sk-andprivate. A Supabase anon or publishable key is designed to be public. A Supabase service role key, a Stripe secret key or an OpenAI key in the browser means anyone can use it on your bill or against your data. - No secrets in your code history. In your GitHub repository, search for
.env,keyandsecret, and check that.envis listed in.gitignore. Deleting a key from the latest version does not remove it from Git history. If a real key was ever committed, treat it as leaked. - Keys are restricted and rotated. Any key that was exposed, pasted into a chat or shared with a freelancer should be rotated (replaced with a new one in the provider's dashboard). Where providers allow it, restrict keys to your domain and set spending limits, so a leaked key cannot run up an unlimited bill.
2. Database access rules
- Row level security is on for every table. In Supabase, every table that the browser can reach needs row level security enabled. The table editor marks tables where it is off, and the Security Advisor in the dashboard flags them too. Supabase's own guide to row level security (opens in a new tab) explains how policies work. If you use Firebase, read your security rules (opens in a new tab) and make sure nothing still allows open reads or writes from test mode.
- Policies actually restrict rows. A policy that is switched on but allows everything is no better than none. Read each policy: for customer data, it should compare the row's owner with the logged-in user, not just check that someone is logged in. Then test it: log in as user A, note the ID of one of A's records, log in as user B and try to load it.
- Users cannot edit sensitive columns. If your profiles table has a column such as
role,is_admin,planorcredits, check that a user cannot change it on their own row. In practice: as a normal user, update your profile, then look at the request in the Network tab. If the request could includerole: "admin"and the database would accept it, anyone can make themselves an admin.
3. Authentication and sessions
- Private data needs a login on the server, not just in the page. Log out, then paste the URL of a private page, and separately repeat one of the data requests you saw in the Network tab. Hiding a page behind a login screen does nothing if the data behind it is returned to anyone.
- Sign-up, reset and login behave properly. Check that new accounts must confirm their email if that matters for your app, that password reset links expire and work only once, and that trying a wrong password twenty times in a row slows down or blocks further attempts.
- Your own admin accounts are protected. Turn on two-factor authentication for every account that controls the app: the AI builder, GitHub, Supabase or Firebase, hosting, the domain registrar, the payment provider and your email. An attacker rarely needs a bug if they can sign in to your dashboard.
4. Authorisation
- Users can only reach their own records. Wherever a URL or request contains an ID, change it to another user's ID and see what happens. This is the single most common serious bug in AI-built apps, and it is easy to test.
- Admin actions are checked on the server. As a normal user, find the request an admin action sends (delete a user, change a price, issue a refund) and try sending it. A hidden button is not a permission. The server or the database rule must refuse it.
- Teams and companies are kept apart. If your app has teams, workspaces or client accounts, create two and check that no list, search, export or report shows data from the other. Check that invitations expire and that removing a member really removes their access.
5. Input validation
- The server checks every value that matters. Prices, quantities, discount codes, dates and email addresses must be checked on the server, not just in the form. A simple test: in developer tools, edit a form request to send a negative quantity or a price of 1, and see whether it is accepted.
- What users type is shown safely. Put
<b>test</b>in your name or a comment field and see where it is displayed. If the text turns bold, the app is inserting raw HTML, which can let one user run scripts in another user's browser. Ask the AI to list every place the code inserts raw HTML into the page. - No query is built by gluing in user text. Ask your AI tool to list every place where user input goes into a database query, an edge function or a prompt for an AI feature. Queries should use the database library's parameters, never string joining. If your app has an AI feature, check that a user cannot type instructions that make it reveal other users' data or its hidden instructions.
6. Payments and webhooks
- The price comes from the server. Watch the request your checkout sends. If it contains the amount, try changing it. The server should look up the price from its own records and create the checkout session itself.
- Payment is confirmed by a verified webhook. Access or credits should be granted only when the payment provider's webhook confirms the payment, and the webhook handler must verify the provider's signature. Stripe's webhook documentation (opens in a new tab) describes this, and Razorpay works the same way. If you are choosing between them, see Razorpay vs Stripe.
- The success page grants nothing. Open your payment success URL directly, without paying. Nothing should change. Then check that the same webhook event arriving twice does not add credits or create a second order: providers retry webhooks, so this happens in normal use.
7. File uploads
- Type and size are limited on the server. Try uploading a very large file, and a file renamed to look like an image. Both should be refused by the server or the storage rules, not just by the file picker.
- Private files are actually private. Upload an invoice or ID document as one user, copy its URL, and open it in a private browser window while logged out. Private files belong in private storage buckets and should be served through signed links that expire.
- Users cannot overwrite each other's files. Storage rules should restrict each user to their own folder, and the server, not the browser, should choose the final file name and path.
8. Dependencies
- Every package is real and needed. AI tools sometimes suggest packages that do not exist, or that have names very close to popular ones, and attackers register those names. Look through
package.jsonand check that each unfamiliar name is a real, maintained package on npm. Remove anything unused. - Known vulnerabilities are checked. Run
npm audit(or ask your AI tool to) and turn on Dependabot alerts in your GitHub repository settings, so you hear about vulnerable packages without remembering to look. - Versions are locked. The lockfile (
package-lock.json,pnpm-lock.yamlor similar) should be committed, so the build that runs in production uses the same versions you tested.
9. Logging and errors
- Errors do not reveal internals. Trigger a few errors on purpose: a broken URL, a form with nonsense in it. Users should see a plain message, not a stack trace, a database error or a configuration value.
- Logs and the console hold no secrets. Open the Console tab on your live site and use the app. AI-generated code often leaves
console.loglines that print whole user objects, tokens or API responses. Server logs should not store passwords, tokens or full payment details either. - You are told when something fails. Set up an error monitoring service or at least email alerts for failed payments, failed webhooks and server errors. If you hear about a broken checkout from a customer, you are hearing too late.
10. Backups and deploys
- A backup exists and you have restored it. Check what your database plan actually backs up and for how long, because not every plan includes backups you can restore. Then restore one into a test project. A backup you have never restored is a hope, not a backup.
- Test and live are separate. Use a separate database project and test payment keys for development, and keep production credentials away from AI agents. There have been widely reported cases of AI coding agents deleting live data when they were given access to production.
- Code is in Git and you can roll back. Every change should go through a repository you own, and you should know how to put the previous version back in minutes. If you deploy a Laravel backend yourself, my guide to deploying Laravel on cPanel or a VPS covers a deploy you can repeat.
Prompts to ask the AI to review its own code
AI tools are useful reviewers if you ask narrow questions and ask for evidence. Broad prompts such as "make my app secure" tend to produce reassuring summaries and a few cosmetic changes. Ask for lists with file names instead, one area at a time:
List every API key, secret or token referenced anywhere in this project.
For each one, give the file and line, say whether that code runs in the
browser or on the server, and say whether the value is safe to be public.
Do not change any code yet.
List every database table this app reads or writes. For each table, show
the row level security policies that apply to select, insert, update and
delete, and explain in plain English who can read and change each row.
Flag any table where a logged-in user could read or edit another user's rows.
Walk through the payment flow step by step, from the checkout button to
the moment the user gets access. Show where the price is decided, where the
payment is confirmed, whether the webhook signature is verified, and what
happens if the same webhook arrives twice.
Find every place where user input reaches a database query, an HTML
render, a file path, or an AI prompt. For each, say how the input is
validated or escaped, and list the ones that are not.
Ask it to report before it changes anything, then fix one finding per prompt, and re-run the relevant manual test from the checklist after each fix.
The limits of asking the AI
This kind of review catches real problems, and it is worth doing. It also has limits you should know about:
- It cannot see your dashboards. Policies, bucket settings and key restrictions set in Supabase, Firebase or your payment provider may not be in the code at all. The AI may describe rules that do not match what is actually live.
- It tends to agree with you. If you ask "is this secure?", the answer leans towards yes. Ask it to find problems, not to confirm there are none.
- It may say it fixed something it did not. Always test the behaviour yourself: log in as the second user, open the success page, try the edited request.
- It reviews what it wrote with the same blind spots it wrote it with. A second opinion from a different tool or a person helps because it does not share the original assumptions.
None of this replaces testing the running app as different users. That is why most items above start with "try" rather than "ask".
Vibe coding security best practices after launch
Passing the checklist once is the start. The vibe coding security best practices that keep an app safe over time are simple habits:
- Keep a short rules file that your AI tool reads with every prompt, listing what must not change: the database policies, the payment flow, where secrets live.
- Make changes on a branch or test copy, and look at what changed before it goes live.
- Re-run the first four sections of this checklist after any large change, and all of it every few months.
- Write down what your app should do before you prompt it to build more. A clear spec cuts down the guesswork that produces insecure code; my PRD template for AI tools is a good starting point.
On Lovable app security specifically: most Lovable projects use Supabase, so sections 1, 2 and 7 matter most. Check them first.
When to get help
If several items in the first four sections fail, or you are not sure how to read what you found, it is worth getting someone to look before launch rather than after an incident. If the app is already breaking, leaking data or stuck in a loop where every prompt makes things worse, my vibe coding cleanup service starts with a read-only audit and a written report of AI generated code security issues, ranked by severity, and then fixes them in that order. If the app works and you need it launched properly, with database rules, payments, email, a custom domain and backups, see Lovable and Bolt app to production. And if you have not built it yet, app idea to product covers planning the first version so many of these problems never appear.