Your secret key is in a VITE_ variable. Everyone can read it.
Last reviewed October 23, 2026 against the providers' current documentation.
The short version
Every variable whose name starts with VITE_ is written into your built JavaScript, so anyone visiting your site can read it. That's fine for values meant to be public, like a Supabase URL and publishable key. A secret key (OpenAI, Stripe sk_, a Supabase secret key, a database password) has to move to server code, such as an edge function, and the old key has to be rotated, because it has already been published.
Why it matters
Vite's documentation says it directly: variables prefixed with VITE_ "will be exposed in client-side source code after Vite bundling", and they "should not contain sensitive information such as API keys". The value isn't fetched at runtime; Vite replaces import.meta.env.VITE_… with the actual text when you build, so the key sits in a file under /assets/ that every visitor downloads. Putting it in a .env file and keeping that file out of git doesn't change this. Anyone who copies the key can use it on your bill or against your data until you turn it off.
Fix it, in this order
Sort your VITE_ variables into public and secret
Open your.envfiles and your host's environment settings and list everyVITE_variable. Values designed to be public can stay: a Supabase project URL and anon orsb_publishable_key (as long as Row Level Security is on), a Stripepk_publishable key, a Firebase web config, an analytics site ID. Anything that's called a secret, a private key, a password, or a server key by the service that issued it has to go.Move each secret into server code
Vite's docs recommend a backend server or serverless/edge functions for secrets. Create one small function per job, for example a Supabase Edge Function that calls OpenAI. Store the key there as a secret (Supabase: Edge Function Secrets in the dashboard, orsupabase secrets set NAME=value) and read it withDeno.env.get('NAME'). Give it a name without theVITE_prefix, require a logged-in user, and have the frontend call the function instead of the provider.Don't work around the prefix
Don't setenvPrefixto an empty string (Vite now refuses, because it would expose every variable) or to a prefix that matches your secret names, and don't push a secret into the browser withdefine. Any value the browser code can read is public, whatever it's called.Rotate every key that was in a VITE_ variable
Removing the variable and rebuilding stops new copies, but the old JavaScript is already in browser caches, CDN caches and archives. Create a new key at the service that issued it, put it only in the server function, deploy, then revoke the old key. Our guides walk through this for OpenAI, Stripe and Supabase keys.Keep local .env files out of git and off your host
Vite says.env.*.localfiles can hold sensitive values and should be ignored with*.localin.gitignore. Serve only the build output (dist/), never the project folder; if your.envfile can be downloaded from your site, see .env file exposed.Check the build, then the live site
Runnpm run buildand search thedist/folder for the start of each secret (for examplesk_live_orsk-proj-). Nothing should match. After deploying, run the free check on your live site, which reads the same JavaScript files a visitor gets.
Or ask your AI tool to do it
Paste this into Lovable, Bolt, Cursor, Claude Code or whichever tool built your app. Review what it changes before you deploy.
This is a Vite app. List every environment variable that starts with VITE_ (in .env files and in the code via import.meta.env) and tell me for each one whether it is designed to be public (for example a Supabase URL and anon/publishable key, a Stripe pk_ key, a Firebase web config) or a secret. For every secret: move the code that uses it into a server-side function (a Supabase Edge Function or the backend this app already has), read the key there from a server-only environment variable without the VITE_ prefix, require an authenticated user and rate-limit the function, and change the frontend to call that function. Do not use define or envPrefix to get the secret into the browser. Make sure .gitignore contains *.local and .env. Finally, list the keys I must rotate at each provider because they were already published.
How this happens
AI app builders and tutorials put configuration in .env files, and the first time a variable comes out undefined in the browser, the quickest fix is to add the VITE_ prefix. It works immediately, so nobody notices that the prefix is exactly what makes the value public. Lovable's docs, for example, note that its React + Vite projects embed VITE_ variables at build time; the same applies to any Vite app, whichever tool wrote it.
Check that it's really fixed
After you deploy, run the free check. It reads your live site the way an attacker would and tells you if any secret key is still visible.
Check my app freeTopic check: Exposed API key checker
Questions
Are Vite environment variables public?
Only the ones exposed to client code: those starting with VITE_ (or your custom envPrefix), plus Vite's built-ins like MODE. Those are copied into the built JavaScript and anyone can read them. Variables without the prefix aren't available through import.meta.env in browser code.
Is it OK to keep my Supabase URL and anon key in VITE_ variables?
Yes. The Supabase URL and the anon or sb_publishable_ key are meant to be public, but only Row Level Security protects your tables. See Is the Supabase anon key safe?. Never put the service_role or sb_secret_ key in a VITE_ variable.
If I remove the VITE_ prefix, is the key hidden?
The key stops being bundled, but the browser code that used it will get undefined and break. The code that needs the key has to move to a server function. Renaming alone isn't a fix, and neither is the old key: it was already public, so rotate it.
My .env file is in .gitignore. Isn't that enough?
No. .gitignore keeps the file out of your repository, but Vite still reads it when you build and copies every VITE_ value into your JavaScript.
Does this apply to Next.js too?
The same idea, with a different prefix: Next.js puts NEXT_PUBLIC_ variables into browser code. Other tools have their own public prefixes, such as EXPO_PUBLIC_ in Expo. Treat any variable with a public prefix as published.
Can your free check see my VITE_ variables?
It can't see variable names, because Vite replaces them with their values at build time. It reads your public JavaScript and flags values that look like secret keys (OpenAI, Anthropic, Stripe, Supabase secret, AWS, GitHub, database URLs with passwords and others) and random-looking values in fields named like secrets. It's passive, so it never tries to use a key it finds.
Sources
- Vite: Env variables and modes (Protecting secrets)
- Vite: Shared options, envPrefix
- Supabase: Edge Functions environment variables (secrets)
- Lovable: Deploying and hosting outside Lovable