Environment Variables Security: Stop Leaking Secrets to Production

Quick answer: Environment variables only protect secrets when used correctly. Any variable prefixed VITE_, NEXT_PUBLIC_, or REACT_APP_ is embedded in your JavaScript bundle and readable by every visitor. Keep API keys, service role keys, and database URLs server-side without a public prefix and verify your bundle contains no secrets.

By Gabriel CA · Kraftwire Software

· 10 min read

Environment variables are only secure when they remain on the server; any variable prefixed with VITE_, NEXT_PUBLIC_, or REACT_APP_ is automatically baked into your frontend JavaScript bundle and is readable by any visitor using browser DevTools. To stop leaking secrets, you must remove these public prefixes from sensitive keys (like OpenAI or Stripe Secret keys), add .env to your .gitignore before your first commit, and use server-side functions or Edge Functions to handle private API calls.

Why Are Environment Variables a Security Trap?

Environment variables are the industry standard for separating configuration from code, but they are frequently misunderstood by both human developers and AI coding agents. The "trap" lies in the assumption that because a value is stored in an environment variable, it is inherently hidden from the end user. This is false. In most modern web frameworks, environment variables are a transport mechanism, not a security layer.

When you use an AI tool like Cursor, Windsurf, or Lovable, the AI often prioritizes "making the app work" over "keeping the app secure." If the frontend needs to call an API, the AI might suggest adding the API key to your .env file with a public prefix so the frontend can access it. This immediately exposes that key to the world.

The Critical Split: Client-Side vs. Server-Side

To master environment variable security, you must understand where your code actually executes.

  • Server-Side Execution: This includes Node.js backends, Next.js Server Components, and Edge Functions (like Supabase Edge Functions or Vercel Functions). Environment variables here are private. They stay on the server and are never sent to the user's browser.
  • Client-Side Execution: This includes React, Vue, or Svelte components that run in the user's browser. Because the browser needs to know the value of the variable to use it, the build tool (Vite, Webpack, etc.) literally replaces the variable name with the actual secret string inside your .js files during the build process.

If you put a secret in the client-side bundle, you are effectively handing your credit card to every visitor and asking them not to look at the numbers.

Which Prefixes Make Environment Variables Public?

Frameworks use specific naming conventions to prevent accidental leaks, but these only work if you follow them. Any variable starting with these prefixes will be visible to the public:

  • Vite (Lovable, Bolt.new, Windsurf): VITE_ (e.g., VITE_STRIPE_KEY)
  • Next.js (v0, Cursor): NEXT_PUBLIC_ (e.g., NEXT_PUBLIC_ANALYTICS_ID)
  • Create React App: REACT_APP_ (e.g., REACT_APP_MAPBOX_TOKEN)
  • Nuxt: NUXT_PUBLIC_ (e.g., NUXT_PUBLIC_BASE_URL)

The AI Hallucination Risk

AI tools often "hallucinate" security by suggesting you prefix everything to "ensure the frontend can see it." In our vibe coding security checklist, we highlight that AI-generated .env files are a primary source of data breaches.

Dangerous AI-Generated .env:

Secure .env Configuration:

Why .env Files Must Never Be Committed

A common mistake in the "vibe coding" era is committing the .env file to GitHub or GitLab. AI tools often generate this file in the root directory, and if your .gitignore isn't set up immediately, that file enters your git history.

Even if you delete the file in a later commit, it remains in the git history forever. Anyone with access to the repository can browse previous commits and find your live production secrets. According to recent security research, .env files are a primary target because they store secrets in plain text, which can leak through "Docker layers, ps output, and child processes."

How to Fix a Committed Secret

If you have already pushed a secret to a repository:

  • Revoke the old key and generate a new one.
  • Update .gitignore: Add .env and .env.local immediately.
  • Purge History: Use a tool like git filter-repo or BFG Repo Cleaner to scrub the file from all past commits.
  • Scan: Use a secret scanner to ensure no other fragments remain.

Vercel and Next.js

Vercel has improved its security posture significantly. This prevents secrets from accidentally appearing in deployment logs if a build script prints them. However, this is a "precise gate," not a blanket mask, so shorter secrets may still be at risk.

Supabase and Lovable

When building with Lovable, your frontend uses the VITE_SUPABASE_ANON_KEY. This is safe because it is restricted by Row Level Security (RLS). However, the SUPABASE_SERVICE_ROLE_KEY must never have a VITE_ prefix. It should only be used in Supabase Edge Functions, where it is accessed via Deno.env.get().

Windsurf and Cursor

When using local AI agents like Windsurf or Cursor, be wary of the .env files they create. Always run an application security audit before your first deployment to ensure the agent hasn't hardcoded a "fallback" secret in the code itself.

Common AI-Driven Security Mistakes

1. Hardcoded Fallbacks

AI often writes code like this to prevent crashes:

If the environment variable fails to load, the AI's "default" key is baked into the code. This is a critical leak.

2. Debug Logging

AI tools frequently insert console.log(process.env) or console.log(import.meta.env) to help you debug connection issues. If these logs are not removed, your entire environment configuration is printed to the browser console for every visitor to see.

3. Client-Side Logic for Secrets

Never perform sensitive checks in the frontend. An AI might suggest:

Because VITE_ADMIN_CODE is in the JavaScript bundle, a malicious user can simply search the source code for the "secret" code and bypass your check.

The Ultimate Environment Variable Audit Checklist

Before you ship your next AI-built project, perform these ten checks:

  • Prefix Check: Do any variables with VITE_, NEXT_PUBLIC_, or REACT_APP_ contain private secrets?
  • Git Check: Is .env explicitly listed in your .gitignore?
  • History Check: Run git log -p .env to see if the file was ever committed in the past.
  • Log Check: Search your codebase for console.log statements containing env or process.
  • Fallback Check: Search for || "sk_" or other hardcoded string patterns in your source code.
  • Build Check: Run a local build (npm run build) and search the dist or .next folder for your secret keys.
  • Platform Check: Ensure "Sensitive" toggles are enabled in your hosting provider (Vercel, Netlify).
  • Rotation Check: Have you rotated your keys in the last 90 days?
  • Scope Check: Are your development keys different from your production keys?
  • Automated Scan: Use a free security scanner to detect exposed API keys and .env files automatically.

How to Verify Your App is Safe

You don't have to guess if your secrets are leaking. Follow these steps to verify:

1. Inspect the Network Tab

Open your site, press F12, and go to the Network tab. Filter by "Fetch/XHR." Look at the headers of the requests your app is making. If you see an Authorization: Bearer sk-... header being sent directly to OpenAI or Anthropic from the browser, your key is exposed. These calls should happen through your own backend.

2. Search the Source Tab

In the browser DevTools, go to the Sources tab. Press Ctrl+Shift+F (or Cmd+Option+F on Mac) and search for the first few characters of your secret key (e.g., sk-proj). If it appears in a .js file, it is public.

3. Use SimplyScan

SimplyScan's engine is designed specifically for vibe-coded apps. It detects exposed API keys, missing security headers, and env-var leaks in ~30 seconds.

Key Takeaways for Vibe Coders

The speed of AI development is incredible, but it often bypasses the "boring" security fundamentals. Environment variables are the first line of defense. By keeping private keys strictly server-side and ensuring your .env files never touch GitHub, you protect your users and your cloud billing account from exploitation.

If you are unsure if your AI tool has introduced a leak, you can use our free scan to check your site's health across 8 dimensions, including security and GDPR compliance signals.

Related Guides

Related Free Tools

faq:

  • q: Is it safe to put my Supabase anon key in a VITE_ variable?

a: Yes. The Supabase anon key is designed to be public and belongs in a client-side variable like VITE_SUPABASE_ANON_KEY. Its safety relies on Row Level Security (RLS) policies. However, you must never prefix the SERVICE_ROLE_KEY with VITE_, as that key bypasses all security and should only exist in server-side Edge Functions.

  • q: I committed my .env file to GitHub. What should I do first?

a: You must revoke and rotate every exposed key immediately. This is the only way to ensure security. After rotating, add .env to your .gitignore, use a tool like BFG Repo Cleaner to remove the file from your git history, and force push the changes. Simply deleting the file in a new commit is insufficient.

  • q: How can I tell if a secret is in my JavaScript bundle?

a: Open your deployed site, press F12 for DevTools, and search the Sources tab for your secret key values. You can also build your app locally and run grep -r "your-secret-key" dist/. If the key appears in any generated JavaScript file, it is exposed and the public prefix must be removed.

  • q: What is the difference between .env and .env.example?

a: .env contains your actual secrets and must never be committed to version control. .env.example is a template that contains the variable names but uses placeholder values (e.g., OPENAI_API_KEY=your_key_here). This allows collaborators to see which variables are required without exposing real credentials.

  • q: Do environment variables need to be different for development and production?

a: Yes. Using separate keys for development and production prevents a leak in your local environment from compromising production data. Most platforms like Vercel and Netlify allow you to scope variables specifically to "Development," "Preview," or "Production" environments to maintain this critical isolation.

  • q: Can environment variables leak through console.log?

a: Yes. If you log an environment variable in client-side code, its value is sent to the browser console where any user can see it. AI tools often leave these logs in for debugging. Always remove console.log calls that reference process.env or import.meta.env before deploying to production.

meta_description: Stop leaking API keys.

meta_title: Environment Variables Security: Stop Leaking Secrets

title: Environment Variables Security: Stop Leaking Secrets to Production

excerpt: Environment variables only protect secrets when used correctly. Any variable prefixed VITE_, NEXT_PUBLIC_, or REACT_APP_ is embedded in your JavaScript bundle and readable by every visitor. Keep API keys, service role keys, and database URLs server-side without a public prefix and verify your bundle contains no secrets.

Frequently asked questions

Is it safe to put my Supabase anon key in a VITE_ variable?

Yes. The Supabase anon key is designed to be public and belongs in a client-side variable like VITE_SUPABASE_ANON_KEY. Its safety relies on Row Level Security (RLS) policies. However, you must never prefix the SERVICE_ROLE_KEY with VITE_, as that key bypasses all security and should only exist in server-side Edge Functions.

I committed my .env file to GitHub. What should I do first?

You must revoke and rotate every exposed key immediately. This is the only way to ensure security. After rotating, add .env to your .gitignore, use a tool like BFG Repo Cleaner to remove the file from your git history, and force push the changes. Simply deleting the file in a new commit is insufficient.

How can I tell if a secret is in my JavaScript bundle?

Open your deployed site, press F12 for DevTools, and search the Sources tab for your secret key values. You can also build your app locally and run grep -r "your-secret-key" dist/. If the key appears in any generated JavaScript file, it is exposed and the public prefix must be removed.

What is the difference between .env and .env.example?

.env contains your actual secrets and must never be committed to version control. .env.example is a template that contains the variable names but uses placeholder values (e.g., OPENAI_API_KEY=your_key_here). This allows collaborators to see which variables are required without exposing real credentials.

Do environment variables need to be different for development and production?

Yes. Using separate keys for development and production prevents a leak in your local environment from compromising production data. Most platforms like Vercel and Netlify allow you to scope variables specifically to "Development," "Preview," or "Production" environments to maintain this critical isolation.

Can environment variables leak through console.log?

Yes. If you log an environment variable in client-side code, its value is sent to the browser console where any user can see it. AI tools often leave these logs in for debugging. Always remove console.log calls that reference process.env or import.meta.env before deploying to production.

Related guides

  • How to Remove Secrets from Environment Variables and Git History · To remove secrets from environment variables, you must rotate compromised credentials immediately and use tools like git filter-repo or BFG Repo-Cleaner to scrub them from Git history. Simply deleting the file is insufficient. Migrate to a dedicated secret manager to prevent plain-text exposure in .env files or system process lists.
  • Managing Your Cursor Library: How to Index Code Without Leaking Secrets · Manage your Cursor library by enabling Privacy Mode and using a .cursorignore file to exclude sensitive data. While indexing improves AI context, it can leak secrets if hardcoded keys are included. Use SimplyScan to detect exposed credentials before they are indexed into the LLM context window.
  • Supabase Security Checklist: Protect Your Database in Production · Supabase is not secure by default because the anon key is public; you must enable Row Level Security (RLS) on every table to prevent unauthorized access. To protect production apps, keep the service_role key server-side, enforce email confirmation, enable leaked-password protection, and restrict Edge Function CORS to your domain.
  • Webhook Signature Verification: Stop Trusting Unsigned Payloads · Verify every webhook by computing the HMAC-SHA256 of the raw request body using your shared secret and comparing it to the provider's signature header with a timing-safe function. Add a five-minute timestamp tolerance and event-ID deduplication to prevent replay attacks. Without this, anyone can forge events to your endpoint.

All security guides · Free security tools · Platform scanners · Security checklist