Keys that end up in the browser bundle
Hardcoded API Key or Secret
Why this workflow produces it
A Lovable project is a Vite single-page app. There is no server in it, so there is nowhere in the generated project for a secret to live — every string in the repository is compiled into JavaScript that anyone can download and read.
The moment you add a database — and Supabase is the usual answer — the project gains a client config with a key written inline. The codebase now demonstrates that pattern before you have asked for your first real feature. When the next prompt is "add Stripe" or "send an email when someone signs up", the fastest completion is the one that follows the file already in front of it, and a provider key lands beside the one that was always meant to be public.
Moving it to an environment variable does not help by itself. Vite inlines every VITE_-prefixed variable into the bundle at build time, so a secret in .env is a secret on your CDN.
The change that prevents it
Split credentials into two buckets before you prompt, and say which is which. The Supabase anon key is publishable by design — the control protecting that data is row-level security, not secrecy. Everything else (a service-role key, a Stripe secret key, any provider key that can spend money or read another user's data) must never reach the client at all: it belongs in a Supabase Edge Function or another server, with the browser calling that endpoint.
Paste this into your project instructions, or into the prompt itself:
Never put a secret in client code or in a VITE_-prefixed env var — Vite inlines both into the browser bundle. Any key that can spend money, send mail, or read another user's data goes in a Supabase Edge Function and is read from Deno.env.get() there. The browser calls the function. The Supabase anon key is the only key allowed in the client, and every table it can reach must have RLS enabled.
The scan that verifies it
Run a scan and confirm the count goes to zero. If a hit is the anon key or another value that is genuinely meant to be published, mark that line reviewed rather than deleting the rule.
npx xploitscan scan . -f json \ | jq '[.findings[] | select(.rule == "VC001")] | length'
Honest caveat. Some of these are the Supabase anon key, which is publishable. That is exactly why the fix is to sort your keys rather than to silence the rule: the same check is what catches the service-role key sitting three lines below it.