ShipWatchFree scan

VITE_ variables are public: what belongs in them (and what doesn't)

Every variable with a VITE_ prefix is pasted into your JavaScript when you build, so any visitor can read it. This page shows you how to prove that on your own app, which values are fine to ship, and where the secret ones go.

Short answer: Yes, they're exposed. Vite replaces import.meta.env.VITE_* with the literal value at build time, so it ends up in the files your users download. Publishable keys and URLs are fine there. Secret keys belong in a server function's environment, read with no VITE_ prefix.

This is the "why" page for Vite-based apps, including most apps built with Lovable and Bolt. If you already know which key leaked, go to the provider page, such as OpenAI key exposed or Stripe key exposed.

What Vite actually does with VITE_

Vite loads your .env, .env.local, .env.production and similar files when it builds. Variables whose names start with VITE_ become available as import.meta.env.VITE_NAME in client code. During the production build, Vite replaces each reference with the value itself. The prefix exists precisely so you choose what becomes public; Vite's docs say VITE_* variables should not contain sensitive information.

See it on your own build [manual]

Say your .env contains:

VITE_SUPABASE_URL=https://abcd1234.supabase.co
VITE_OPENAI_KEY=sk-proj-EXAMPLE
DATABASE_URL=postgres://...

and a component does fetch(url, { headers: { Authorization: `Bearer ${import.meta.env.VITE_OPENAI_KEY}` } }). Run npm run build, then search the output:

grep -r "sk-proj-" dist/assets/

You'll find sk-proj-EXAMPLE sitting in a minified file such as dist/assets/index-3f9a1c.js. DATABASE_URL won't be there, because it lacks the prefix. That minified file is exactly what your host serves to every visitor.

Safe vs. unsafe in a VITE_ variable

Fine to ship (designed to be public):

Never in a VITE_ variable:

A useful test: if someone with this value could spend your money or read data they shouldn't, it's a secret.

Where secrets go instead

A Vite app is static files with no server of its own, so secret work has to move to a function that runs elsewhere:

List every VITE_ environment variable this app uses and classify each one as public (URLs, publishable or anon keys, analytics IDs) or secret (anything that can spend money, write data or bypass access rules). For each secret one, move the code that uses it into a server-side function that reads it without the VITE_ prefix, make the frontend call that function, and delete the VITE_ variable.

Then rotate every secret that was ever in a VITE_ variable on a deployed build [manual]. Removing it from the code doesn't un-leak the old copy.

Make it stick

What the free scan checks here (and what it doesn't)

The scan reads your deployed HTML and up to 25 linked scripts and flags Stripe, OpenAI, Anthropic, AWS, Google and Supabase secret keys by their known formats. It reports a Supabase anon key as info only, since it's public by design, and doesn't test your tables. It also checks whether /.env or /.git/HEAD is publicly served, plus HTTPS and four security headers.

It can't tell that a custom value like VITE_ADMIN_TOKEN is secret. Check those with the classification step above [manual].

FAQ

Are variables in my .env file secret if they don't start with VITE_?
From the browser bundle, yes: Vite only exposes VITE_-prefixed variables to client code by default. But the .env file itself must never be deployed to your public web root or committed to a public repo, or the whole file is readable.
Can I change envPrefix so my secrets stay hidden?
No. envPrefix only changes which names Vite inlines. Anything your client code reads is in the bundle whatever it's called. Vite's docs warn against an empty prefix for exactly this reason.
How does the free scan help with VITE_ variables?
It reads your built page and up to 25 linked scripts, which is where VITE_ values end up, and flags known secret key formats: Stripe, OpenAI, Anthropic, AWS, Google and Supabase secret keys. It can't recognize custom secrets with no known prefix.

Scan your app free in 30 seconds

Paste your live URL for a plain-English report with fix prompts. If you keep shipping, weekly monitoring re-checks it: Solo $19/mo or Pro $49/mo.

Scan my app free