pwnmyvibecode_

Vibe coding security: the complete checklist

Securing an app built with an AI agent means checking the parts you never see while clicking around it: secret keys kept on the server, database access rules switched on, logins enforced on the server, security headers set and no private files published. The checklist at the end of this page covers each one in the order worth doing them.

Updated

Check your live site now

Free, no signup, read only. A grade and plain fixes in seconds.

Why AI-built apps need a security check before launch

AI coding tools are good at making things work. Ask for a dashboard that reads from Supabase and you get one in minutes. Security problems don't show up when you use your own app, so nothing prompts you or the agent to look for them. A leaked key or an open database table behaves exactly like a correct one until someone else finds it.

The platforms themselves are generally solid. Lovable, Bolt, Replit, Vercel, Netlify, Supabase and Firebase run on secure infrastructure and serve your site over https. The risk sits in the app built on top: which keys went into the front end, which tables anyone can read, which routes check who is asking. Those decisions are made in your project, often by the agent, and they're yours to verify.

The common problems are well known and most are quick to fix. The sections below go roughly in order of how much damage each one can do.

Keep secret keys out of the browser

Anything your front end can read, a visitor can read. That includes every environment variable your build tool passes to the browser: NEXT_PUBLIC_ in Next.js, VITE_ in Vite, REACT_APP_ in Create React App and EXPO_PUBLIC_ in Expo. Those prefixes mean "copy this value into the public JavaScript", where View Source and DevTools show it to anyone.

Some keys are designed to be public. Others hand over your account:

  • Fine in front end code: the Supabase anon or publishable key, the Firebase web config apiKey, the Stripe publishable key (pk_live_), and Google Maps keys once they're restricted to your domain. They identify your project; the access rules behind them do the protecting.
  • Never in front end code: the Supabase service_role or secret key (sb_secret_), which bypasses row level security; Stripe secret and restricted keys (sk_live_, rk_live_); OpenAI (sk-) and Anthropic (sk-ant-) keys; AWS access keys (AKIA); GitHub tokens (ghp_); and any database connection string.

If a secret key ends up in public code, rotate it with the provider first, then fix the code. Deleting it from your source isn't enough on its own: old deployments, caches and git history still contain it, and anyone who copied it still has it. Then move the call that needs the key into a server route (an API route, a server action or an edge function) so the key only ever exists on the server.

Check the repository as well. A .env file committed to a public GitHub repo is as exposed as a key in your JavaScript, and it stays in the git history after you delete the file.

Turn on database access rules

If your app talks to Supabase or Firebase straight from the browser, anyone with the public key can send queries to your database, and everyone has the public key. The access rules are the only thing between a visitor and your whole users table.

In Supabase those rules are row level security (RLS). With RLS on and no policies, the public key can read nothing from a table. Each policy then opens exactly what's needed, for example "a signed in user can read rows where user_id = auth.uid()". Tables an agent creates through SQL or migrations don't always have RLS switched on, so check every table in the public schema. The Security Advisor in the Supabase dashboard lists tables without it. You can also test a table with the public key, the same way an outsider would:

terminal
curl 'https://YOUR-PROJECT.supabase.co/rest/v1/profiles?select=*' \
  -H 'apikey: YOUR_PUBLIC_KEY'

If that returns other people's rows, the table is open to anyone. An empty list means either RLS blocked the read or the table is empty; the dashboard tells you which.

In Firebase the equivalent is Security Rules for Firestore, Realtime Database and Storage. Rules left in test mode let anyone read and write until the date written in the rule. Replace them with rules that check request.auth, and use the Rules Playground in the Firebase console to try a read as a signed out user.

An outside scan can't see your database rules. It doesn't have your login and doesn't query your tables, so this is a check you run yourself.

Enforce logins and permissions on the server

Hiding a button isn't access control. If the admin page is only missing from the menu, or an API route trusts a userId sent by the browser, anyone can call it directly with a different value. Every server route, server action and edge function that reads or changes data needs to check who is signed in and whether that person may touch this particular record.

  • Use your platform's auth (Supabase Auth, Firebase Auth, Clerk, Auth.js) rather than a login system the agent wrote from scratch.
  • Take the user's identity from the verified session on the server, never from a field in the request body or URL.
  • Test with two accounts: sign in as one, copy a request from the DevTools Network tab, change the ID to a record owned by the other account and send it again. It should fail.
  • Put rate limits on sign up, login, password reset and any route that calls a paid AI API, so a script can't run up your bill.
  • Mark session cookies Secure and HttpOnly, so they only travel over https and page scripts can't read them.

Set security headers

Security headers are instructions your server sends with every page. They take a few lines of config and each one closes off a specific attack:

  • Strict-Transport-Security makes browsers use https for your domain, even when someone types http.
  • Content-Security-Policy limits which scripts can run, so an injected script is blocked.
  • X-Frame-Options or CSP frame-ancestors stops other sites loading your app in a hidden iframe to trick clicks.
  • X-Content-Type-Options: nosniff stops browsers treating an uploaded file as a script.
  • Referrer-Policy keeps full URLs, which can contain tokens or IDs, from being sent to other sites.

Where they go depends on the host: next.config.ts for Next.js, vercel.json on Vercel, a _headers file on Netlify and Cloudflare Pages, Helmet in Express and add_header in nginx. Start the CSP permissive or in report-only mode, then tighten the script rules to a nonce or hashes once you've seen what it would block.

CORS belongs in this step too. An API that echoes back any Origin together with Access-Control-Allow-Credentials: true lets any website make requests as your signed in users and read the answers. List your own domains explicitly instead.

Make sure private files aren't published

Build tools copy whatever is in the public folder straight onto the web. A .env file, a config.json with keys or a database export dropped there is downloadable by anyone. On a self-managed server, pointing the web root at the project folder can publish the .git folder too.

  • /.env, /.env.local and /.env.production should return 404 on your live domain.
  • /.git/HEAD and /.git/config should return 404. A public .git folder lets anyone rebuild your source code and its history, including any secret that was ever committed.
  • Production source maps (.map files) let anyone read your original source. They don't leak anything by themselves, but they make any key or internal URL in the code easy to find.

One trap when checking by hand: many single page apps answer every unknown path with index.html and a 200 status. A 200 for /.env doesn't prove the file exists, so look at what came back before assuming the worst.

Check the packages your agent installed

Every package the agent adds is code you now run in production. Two problems are specific to AI-written projects. Models sometimes suggest package names that don't exist, and anyone can publish a package under an invented name, including an attacker who has noticed the model suggesting it. Models also tend to pin whatever version they remember, which may be old and have known vulnerabilities.

  • Before installing an unfamiliar package, look it up on npm or PyPI and check that it exists, who publishes it and that it's actively maintained.
  • Run npm audit or pnpm audit, and turn on Dependabot or a similar tool for the repository.
  • Commit the lockfile so every deploy installs exactly the versions you tested.

Dependencies are invisible from outside the app. No external scan can tell you what's in your package.json.

Security prompts to give your AI agent

Your agent can do much of this review if you ask narrow questions one at a time. "Make it secure" gets a vague answer. These prompts get specific ones:

prompts to paste into your AI agent
1. List every environment variable this project uses. For each, tell me whether it reaches the browser (NEXT_PUBLIC_, VITE_ or similar), what it's for, and whether it's a secret. Move any secret that reaches the browser into a server route.

2. List every database table and whether row level security is enabled. Show the policies for each table and explain in one sentence who can read and write it. Flag any table the anon role can read in full.

3. List every API route, server action and edge function. For each, tell me whether it checks that the user is signed in and owns the record being read or changed. Fix any that don't.

4. Check that no .env, .git folder or config file with keys can be served from the public folder or the web root.

5. List the dependencies you added, confirm each one exists on npm and is maintained, then run npm audit and fix what it reports.

Ask for evidence rather than a yes. "Show me the policy on the profiles table" is more reliable than "is RLS on?", because an agent can answer confidently about code it hasn't actually opened.

What an outside scan can and cannot see

A passive scan looks at your live site the way a visitor's browser does. It requests your pages and a few well known file paths and reads what comes back. That makes it good at some problems and blind to others.

Visible from outside:

  • Secret keys in your HTML and JavaScript bundles.
  • Exposed .env, .git and config files.
  • Missing or weak security headers, and whether http redirects to https.
  • Cookie flags, CORS responses and headers that reveal software versions.

Not visible from outside:

  • Database rules, such as Supabase RLS and Firebase Security Rules.
  • Login and permission logic on your server routes.
  • Injection bugs such as XSS or SQL injection, which need active testing.
  • Private repositories, git history and dependencies.

A clean outside scan means the public surface of your app is in good shape. It says nothing about whether your database rules or auth checks are right; those need the checks described above. The free check on this site covers the visible list using ordinary GET and HEAD requests, with no logins and no attack payloads.

Pre-launch security checklist

  1. Search the built JavaScript for secret key prefixes (sk_live_, sk-, sk-ant-, sb_secret_, AKIA, ghp_) and for service_role. Rotate anything you find, then move it to the server.
  2. Confirm every environment variable with a public prefix is meant to be public.
  3. Make sure .env is in .gitignore and no secret sits in the history of a public repository.
  4. Turn on RLS for every Supabase table in the public schema, or replace Firebase test mode rules, then test a read with the public key.
  5. Check that every server route verifies the session and record ownership. Test it with two accounts.
  6. Add rate limits to auth routes and to anything that spends money per request.
  7. Set HSTS, a CSP (report-only to start), X-Frame-Options or frame-ancestors, nosniff and Referrer-Policy.
  8. Confirm http redirects to https on every domain you use.
  9. Mark session cookies Secure and HttpOnly, and restrict CORS to your own domains.
  10. Confirm /.env and /.git/HEAD return 404 on the live site.
  11. Run npm audit and review every package the agent added.
  12. Scan the live URL, fix what it finds, and scan again after any deploy that changes config, auth or dependencies.

For the first step, a search over the build output works for most front end projects (use dist, build or .next depending on your framework):

terminal
grep -rnoE 'sk_live_[A-Za-z0-9]+|sk-ant-[A-Za-z0-9_-]+|sb_secret_[A-Za-z0-9_-]+|AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36}|service_role' dist/

Questions

Is vibe coding safe?

Building with an AI agent is as safe as the review you do before launch. Agents write working code quickly, and they also repeat well known mistakes such as secret keys in front end code and tables without access rules. Working through the checklist on this page before real users arrive covers the common gaps.

Is it safe to have my Supabase anon key in the front end?

Yes, it's designed to be public. It's only safe because row level security limits what it can do, so make sure RLS is on for every table. The service_role or secret key is the one that must never reach the browser.

Can my AI agent do the security review for me?

It can do a lot of it if you ask specific questions like the prompts above and ask it to show the code or policy behind each answer. Confirm the important answers yourself, for example with a curl against your own table using the public key.

How often should I check my app's security?

Before launch, and again after any deploy that touches environment variables, auth, database tables, hosting config or dependencies. A quick outside scan after each deploy catches regressions such as a key creeping back into the bundle.