Secure your vibe-coded app before launch: a checklist

Exposed API keys, open Supabase tables, sign-in that doesn’t protect anything, missing rate limits: what to check and how to fix it, in plain English.

AI app builders get you to a working app fast. They don’t automatically get you to a safe one. The most common ways vibe-coded apps leak data are boring and fixable: a secret key shipped to the browser, a database table anyone can read, or a “members only” page that only hides a button. This checklist is written for Lovable, Bolt, v0 and Replit users, and most of it applies to any app built on Supabase. Work through it once before you share your link, and again after any big change.

This is a practical starting point, not a professional security audit. If your app handles payments, health data or other sensitive personal data, get a human review too. Lovable’s own docs say the same about its scanners. (Sources linked throughout; checked 2026-10-20.)

1. Keep secret keys out of the browser

Anything that runs in your visitor’s browser can be read by your visitor. That includes “hidden” environment variables, if your framework bundles them.

  • Know the public prefixes. In Vite apps (the default in Lovable and Bolt), any variable starting with VITE_ is bundled into the code that ships to the browser. Vite says these “should not contain sensitive information such as API keys”. In Next.js apps (v0, and many Vercel apps), the same goes for NEXT_PUBLIC_. (Vite, Next.js)
  • Make paid API calls from the server. OpenAI, Anthropic, Stripe secret keys, email providers: call them from a server or edge function, never from the page. Lovable detects keys pasted into the chat, steers you to its Secrets store, and builds a server-side function for the call. (Lovable security)
  • Use the platform’s secrets store. Use Lovable Secrets, Replit Secrets (encrypted, and exposed to your code as environment variables), or Vercel environment variables of the Secret type. Note that Replit says Secrets work for every deployment type except Static Deployments. (Replit, Vercel)
  • Check your live site. Open it, view the page source and the network tab in your browser’s dev tools, and search for sk-, sb_secret_, service_role and secret. If you find a real secret there, treat it as leaked.

2. Use the right Supabase key in the right place

  • The publishable key (sb_publishable_…, or the legacy anon key) is meant to be public. It only reaches what Row Level Security allows.
  • The secret key (sb_secret_…, or the legacy service_role key) bypasses Row Level Security. Supabase says to never put it in a browser, a shipped app or source control. (Supabase API keys)
  • Supabase is deprecating the legacy anon and service_role keys by the end of 2026. If an AI tool tells you to paste a long key starting with eyJ, it’s following old instructions.
  • If a secret key leaked: fix the cause first, create a new secret key, swap it in everywhere, confirm nothing uses the old one, then delete it. (That’s Supabase’s documented order.)

3. Turn on Row Level Security for every table, and mean it

This is the big one. Supabase’s docs warn that a table in an exposed schema without RLS “is readable and writable by any role with a grant on it”. Bolt’s docs put it more bluntly: “A table without RLS can be read and changed by anyone.” (Supabase RLS, Bolt database security)

  • RLS on, on every table. That includes tables you added by hand or that the AI created later.
  • Watch out for policies that say using (true). They let everyone through. Bolt flags these as “RLS Policy Always True”, and notes it may write them on purpose if your site has no sign-in. That’s fine for a public menu or blog. It’s not fine for orders, messages or anything personal.
  • One policy per action (select, insert, update and delete), each limited to the user’s own rows, for example (select auth.uid()) = user_id.
  • Don’t base permissions on user metadata. Signed-in users can edit their own user_metadata, so “role: admin” stored there can be faked. Use app_metadata or a separate roles table instead.
  • Views skip RLS by default. If you expose a view, create it with security_invoker = true (Postgres 15+).
  • File storage has rules too. Uploaded files need their own access policies. Check that private uploads aren’t in a public bucket. (Supabase Storage access control)

Use the built-in scanners. They’re free or close to it. Lovable runs a Quick scan automatically when you publish (database rules, vulnerable npm packages, unauthenticated MCP servers), and you can run a Deep scan of your code from the Security view. Lovable says running scans is free. Bolt’s database Security page runs the same checks as Supabase’s Security Advisor and offers an “Ask Bolt to fix” button. If you use Supabase directly, open the Security Advisor in your dashboard.

4. Make sign-in actually protect things

  • Hiding a button isn’t protection. Every private page, API route and server function must check who is calling it on the server (or through RLS), not only in the interface.
  • Admin actions need an admin check, not just “is signed in”. Lovable’s Deep scan specifically checks that “privileged actions check the caller’s role rather than only that they signed in”.
  • Switch on leaked-password protection. Lovable and Bolt both support checking new passwords against the Have I Been Pwned database. Bolt says it’s on by default for Bolt databases, but isn’t available on the free Supabase plan.
  • Test it signed out. Open a private window and try your private URLs and data directly. Then sign in as a second test user and try to open the first user’s records.

5. Rate limits and spending caps

Bots find new apps quickly. An open endpoint that calls a paid AI model can turn into a large bill overnight.

  • Supabase Auth has built-in rate limits on sign-ups, sign-ins, OTPs and password resets, which you can adjust under Authentication > Rate Limits. These cover auth endpoints only, not your own functions. Supabase’s built-in email provider sends just 2 emails per hour, so set up custom SMTP before launch or your sign-up confirmations will stall. (Supabase Auth rate limits)
  • Put AI features behind sign-in, and limit how often each user can call them.
  • Cap spend at the provider. For example, OpenRouter lets you set a credit limit on each API key, and Lovable has usage limits and alerts on paid plans. Give each app its own key so one leak doesn’t drain everything. (OpenRouter limits)
  • Lock down forms. Contact forms, uploads and “send an email” features are favourite spam targets. Lovable’s Deep scan looks for “functions and routes that anyone can trigger, upload to, or use to send messages without permission”.

6. Payments: never trust the browser

  • Work out prices and what someone paid for on the server, not from values the page sends.
  • Verify Stripe webhook signatures with your endpoint’s signing secret before acting on an event. That way nobody can fake a “payment succeeded” call. (Stripe webhooks)

7. Don’t commit secrets to Git

  • Keep .env files out of your repo. Next.js’s default template already adds them to .gitignore, and Vite’s docs say to ignore *.local files.
  • If you sync to GitHub, push protection can block a push that contains a recognised secret. (GitHub push protection)
  • Deleting a key in a later commit doesn’t remove it from Git history. If a real key was ever pushed, rotate it.

8. The rest of the basics

  • Dependencies: fix critical vulnerabilities in your npm packages. Lovable’s Quick scan audits them, and npm audit does it locally.
  • Untrusted input: anything a user types can end up in a query, a page, an email or an AI prompt. Ask your builder to validate it and escape it before display.
  • Error messages and logs: don’t show stack traces or personal data to visitors.
  • Sharing settings: on Replit, collaborators can see secret values, and anyone who remixes your project can see the secret names. Check who has access before you invite people. (Replit)

For a wider view, the OWASP Top 10:2025 lists Broken Access Control as the number-one web app risk. That’s exactly what sections 3 and 4 above are about.

Copy-paste prompts for your AI builder

  • “Check that row-level security is on for every table in my database, and that each policy allows only the access my site needs.” (Bolt suggests this exact wording.)
  • “Find any API keys or secrets in client-side code or VITE_/NEXT_PUBLIC_ variables and move those calls to a server function that reads the key from secrets.”
  • “For every page, API route and function, list who can call it signed out, signed in, and as an admin. Fix anything private that’s reachable signed out.”
  • “Add per-user rate limiting to every endpoint that calls a paid AI model or sends email.”

The 10-minute pre-launch checklist

  1. No secret keys in the page source or network tab of the live site.
  2. Only the Supabase publishable or anon key in the frontend. The secret or service_role key is server-only.
  3. RLS on for every table, with no using (true) on private data.
  4. Private storage buckets aren’t public.
  5. Private pages and data fail when you’re signed out, and when you’re signed in as a different user.
  6. Admin actions check for an admin role on the server.
  7. Leaked-password protection is on.
  8. AI and email endpoints need sign-in, are rate-limited, and have a spend cap.
  9. Stripe webhooks verify signatures, and prices come from the server.
  10. Your builder’s security scan (Lovable Security view, Bolt Security Audit or Supabase Security Advisor) shows no critical issues.

Next steps: compare hosts in our deploy & hosting ranking, and backends in backend, database & auth. And if you keep running out of AI credits while you fix all this, read AI coding usage limits compared.

FAQ

Is it safe to put my Supabase anon or publishable key in the frontend?

Yes. Supabase designs publishable (and legacy anon) keys to be public, but they only protect you if Row Level Security is on for every table. Never put the secret or service_role key in the frontend, because it bypasses RLS.

Are VITE_ environment variables secret?

No. Vite bundles any variable prefixed with VITE_ into the code that ships to the browser, and its docs say these shouldn’t contain API keys. The same applies to NEXT_PUBLIC_ in Next.js. Keep secrets in server-side functions.

Does Lovable check my app’s security?

Yes. Lovable runs a Quick scan automatically when you publish (database rules, vulnerable dependencies and MCP exposure), and you can run a Deep scan of your code from the Security view. Lovable says running scans is free, and that the scans don’t replace a thorough review for sensitive apps.

What does “RLS Policy Always True” mean in Bolt?

A database policy has no conditions, so any visitor can access those rows. That’s acceptable for public content like a menu or blog posts, but private data needs sign-in plus policies limited to each user’s own rows.

What should I do if I leaked an API key?

Treat it as compromised. Fix how it leaked, create a new key, replace it everywhere, then revoke or delete the old one. Removing it in a later Git commit doesn’t remove it from history.

The Vibe Check

Get “New this week” in your inbox

The Vibe Check, our weekly roundup of new tools for vibe coders, launches soon. Signup opens right here.