While doing routine SEO work on a client's marketing website — a React site built with Vite, first generated by an AI website builder — we noticed it had an admin panel for editing blog posts. The panel asked for an email and password. The check happened entirely in the browser, comparing what you typed with a value stored in a .env file as VITE_ADMIN_PASSWORD. That password was readable by anyone who visited the site.
Why .env doesn't hide anything here
In Vite, any variable whose name starts with VITE_ is copied into the JavaScript bundle when the site is built. That's the whole point of the prefix: it marks values that are safe for the browser. The same goes for NEXT_PUBLIC_ in Next.js and REACT_APP_ in Create React App. The .env file never reaches visitors, but its VITE_ values do, inside the main .js file every browser downloads. In this case the .env file had also been committed to a public GitHub repository, so the password was public twice.
Check your own site in two minutes
Open your site, press F12, go to the Sources (or Debugger) tab and search across all files for words like password, admin, secret, apikey or sk-. Or download your main JavaScript file and search it. If you find a real password or a secret key, assume it's already known. Also look for login checks that only set something like isAdmin in localStorage — anyone can set that value themselves in the browser console.
What is fine to expose
Some keys are designed to be public: Google Analytics IDs, Firebase web config, Stripe publishable keys (pk_), map keys restricted to your domain. What must never be in the browser: admin passwords, Stripe secret keys (sk_), OpenAI or Claude API keys (anyone can run up your bill), database passwords, and service-account files.
The fix
Move the check to a server. The simplest version on Vercel or Netlify is a small serverless function that receives the login, compares it with a hashed password stored in the hosting platform's environment settings (without a VITE_ prefix, so it never reaches the bundle), and sets an httpOnly session cookie. The function that saves blog posts then checks that cookie. A hosted login such as Supabase Auth or Firebase Auth, with database rules that only let admins write, also works. Then change the password, remove the .env file from the repository and its history, and add .env to .gitignore. Making the repository private is good, but it doesn't help on its own — the value is still in the live JavaScript.
Why this keeps happening
AI site builders and starter templates make it easy to add an 'admin page' that works in the preview. It looks protected, it asks for a password, and it behaves correctly for the owner. Nothing about it looks broken until someone opens the developer tools. If your website was built quickly, by a tool or a freelancer, it's worth the two-minute check above.
We flagged it to the client the same day. If you'd like us to check your own website's bundle and admin setup, get in touch — it's usually a short job.
Frequently asked questions
Are Vite environment variables secret?
No. Any variable starting with VITE_ is copied into the JavaScript bundle at build time and can be read by every visitor. The same applies to NEXT_PUBLIC_ and REACT_APP_ variables.
How do I check if my website exposes a password or API key?
Open the site, press F12, and search all source files for words like password, admin, secret, apikey or sk-. Anything real you find should be treated as already leaked and changed.
How should an admin login work on a static or Vite website?
Check the password on a server — for example a serverless function comparing against a hashed password in the hosting platform's private settings and setting an httpOnly cookie — or use a hosted login like Supabase Auth or Firebase Auth with database rules.
Does making my GitHub repository private fix it?
Only partly. It stops people reading the file in the repository, but a VITE_ value is still inside the live website's JavaScript. Move the secret to the server and change it.


