Dev.to Security 🔐 Cybersecurity 👁 0 📖 2 min read

Are VITE_ environment variables secret? No, and here's how keys leak from Lovable and Bolt apps

Short answer: no. In a Vite app, every environment variable that starts with VITE_ is written into your JavaScript bundle at build time. That bundle is downloaded by every visitor, so anyone can read the value in DevTool

Short answer: no. In a Vite app, every environment variable that starts with VITE_ is written into your JavaScript bundle at build time. That bundle is downloaded by every visitor, so anyone can read the value in DevTools.

The same is true of NEXT_PUBLIC_ in Next.js and REACT_APP_ in Create React App.

Why the prefix exists

Vite hides variables without the VITE_ prefix from your frontend code on purpose. The prefix is how you tell Vite "this value is safe to publish".

Here's how secret keys usually end up public in AI-built apps:

  1. The AI tool writes import.meta.env.OPENAI_API_KEY.
  2. It's undefined in the browser, so the feature breaks.
  3. The "fix" is to rename it to VITE_OPENAI_API_KEY.
  4. The feature works, and the key now ships to every visitor.

Lovable, Bolt and most Cursor-built frontends are Vite apps, so this comes up a lot.

Fine in a VITE_ variable

  • Your Supabase URL and anon (or sb_publishable_) key, as long as Row Level Security is on for every table
  • Stripe publishable keys (pk_live_, pk_test_)
  • Firebase config and Google Maps keys restricted to your domain
  • Analytics IDs (PostHog phc_, Sentry DSNs)

Never in a VITE_ variable

  • AI keys: OpenAI (sk-proj-), Anthropic (sk-ant-), Groq, Replicate
  • Stripe secret keys (sk_live_) and webhook secrets (whsec_)
  • The Supabase service_role or sb_secret_ key, which bypasses RLS entirely
  • Email API keys (Resend re_, SendGrid SG.), AWS keys, database URLs with a password

Not sure which kind a key is? I made a free key checker that tells you what a key is and whether it's allowed in the browser. It runs entirely client-side, so the key never leaves the page.

How to move a key out of the frontend

  1. Create a server-side function: a Supabase Edge Function, a Netlify or Vercel function, or an API route.
  2. Store the key as a server secret with no VITE_ prefix, for example supabase secrets set OPENAI_API_KEY=....
  3. Call your function from the frontend instead of the provider. Check the user is logged in inside the function, so strangers can't use it as a free proxy.
  4. Rotate the old key. It's in every copy of your old bundle, including CDN and browser caches.

A prompt that works in Lovable, Bolt or Cursor:

Move every call that uses a secret API key out of the frontend into a
Supabase Edge Function that reads the key from Supabase secrets. Remove
the VITE_ variable for it and call the function instead. Only allow
logged-in users to call it. Don't print any keys in your reply.

Check what your live site actually ships

Your .env file isn't what matters. What matters is what ended up in the built JavaScript. Two ways to check:

  • Open your live site → DevTools → Sources → search all files (Cmd/Ctrl+Shift+F) for sk-, sk_live, service_role and sb_secret_. You should find nothing.
  • Or paste your URL into LeakPatrol, a free read-only scanner I built. It reads every script your site loads, lists any secret keys (masked), and also checks for public .env files, source maps and missing headers.

The full version of this guide, with a FAQ, is here.

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.