Is vibe coding safe? A checklist before you share the link
You built it with Lovable, Cursor, Bolt, v0 or Claude Code and it works. Here are the eight things to check before real people use it, in plain English, with the fix for each.
Vibe coding is safe in the same way that driving is safe: mostly, if you check a few things first. The app working is not the same as the app being safe. AI coding tools are very good at making features run and very quiet about the parts that keep other people's data private.
This is the list we would hand to a friend who just shipped something. Each item says what the problem is, how to tell if you have it, and what to do. None of it needs a security background.
1. Is any secret in the browser?
The problem. Your Stripe key, OpenAI key, database password or Supabase service key ended up in frontend code. Anyone who opens the browser's developer tools can copy it. With an OpenAI key that means running up your bill. With a database key it means reading every user.
How to tell. Search your project for sk_live, sk-, service_role, and any variable starting with NEXT_PUBLIC_ or VITE_ that holds something secret. Those two prefixes mean "ship this to the browser".
The fix. Move the value to an environment variable without a public prefix and only use it in server code: API routes, server actions, or a backend. If the key has already been in a public repo or a deployed bundle, rotate it. Treat it as leaked.
2. Does every API route check who is asking?
The problem. The page hides the button unless you are logged in. The API behind the button does not check. Anyone can call it directly.
How to tell. Open each file under app/api/ or your Express routes. If a route reads or writes user data and never calls something like auth(), getServerSession, supabase.auth.getUser or your own token check, it is open.
The fix. Check the session at the top of every route that touches data, and return 401 if there is none. Then also check that the logged-in user owns the thing they are asking for, not just that they are logged in.
3. Is user input going straight into a database query?
The problem. A query is built with string concatenation or a template literal that includes something from the request. A crafted value can change the query to read, change or delete everything.
How to tell. Look for `SELECT ... ${ or "SELECT ... " +. With Prisma, look for $queryRawUnsafe and $executeRawUnsafe.
The fix. Pass values as parameters so the database treats them as data. Most libraries have a placeholder form. Prisma's regular query methods are already safe; only the raw ones are risky.
4. Are passwords stored as plain text?
The problem. The users table has a password column with the actual password in it. One leak and every user's password is public, including the ones they reuse on their email.
How to tell. Look at how the signup handler saves the password. If it saves req.body.password directly, it is plain text.
The fix. Hash with bcrypt or argon2 before saving, and compare with the library's compare function on login. If you use Supabase Auth, Clerk, Auth0 or NextAuth, they already do this for you.
5. Do login tokens ever expire?
The problem. A JWT created without an expiry is valid forever. If one is stolen, changing the password does not help.
How to tell. Find jwt.sign(. If the options object has no expiresIn, tokens never expire.
The fix. Add expiresIn: '15m' for access tokens and use a refresh token for longer sessions.
6. Is your database open to the internet?
The problem. Supabase, Firebase and similar services let the browser talk to the database directly. That is fine when row-level security is on and the policies are right. It is a disaster when RLS is off or a policy says using (true).
How to tell. In Supabase, open each table and check whether RLS is enabled and what the policies are. In Firebase, open the rules and look for allow read, write: if true.
The fix. Enable RLS on every table with user data and write policies that compare the row's owner to auth.uid(). Test by signing in as a second user and trying to read the first user's data.
7. Is .env in git?
The problem. The file with all your secrets was committed. Even if you delete it later, it stays in the history.
How to tell. Run git log --all -- .env. If anything shows up, it has been committed.
The fix. Add .env to .gitignore, remove it from the repo, and rotate every secret that was in it. Rotation is the part people skip.
8. Do errors reveal your internals?
The problem. A failing request returns the full error message, sometimes with a stack trace, file paths and table names. That is a map for an attacker.
How to tell. Trigger an error on purpose, for example by sending bad JSON to an API route, and look at the response.
The fix. Log the full error on the server and return a short generic message to the client.
Doing this in one command
Items 1 through 5, 7 and 8 are exactly what SixthWall's free scanner checks for. Run this in the project folder:
npx @sixthwall/cli init
It runs on your machine, uploads nothing, and every finding comes with a prompt you can paste into your AI tool to make the fix. Item 6, the row-level security check, is the next rule we are adding because it is the most common serious mistake in Lovable and Bolt projects.
If you only have five minutes, do items 1, 2 and 6. They are the ones that leak other people's data.