Environment Variables Security: Stop Leaking Secrets to Production

Quick answer: Environment variables configure an application; they do not encrypt secrets or guarantee confidentiality. Public prefixes such as VITE_, NEXT_PUBLIC_, and REACT_APP_ can expose values to browser code. Keep privileged credentials server-side, review the actual build configuration, and inspect bundles, responses, logs, and repository history for leaks.

By Gabriel CA · Kraftwire Software

· 12 min read

Treat values exposed through VITE_, NEXT_PUBLIC_, or REACT_APP_ as public client configuration. The exact inclusion rules depend on the framework and build settings. Keep secret credentials in server-side configuration, keep sensitive .env files out of public assets and version control, and review .gitignore alongside tracked files. Removing a prefix alone does not repair a leak: inspect the code path, revoke exposed secrets, and protect any backend endpoint that uses their replacements.

These live figures describe all scanned sites. They do not establish prevalence for an AI-built subgroup, compare development methods, or attribute findings to AI generation.

13% of the 119 live sites SimplyScan has scanned (15 sites) have at least one high-severity web-security issue, such as a missing Content-Security-Policy or HSTS header.

The average security score across the 119 live sites SimplyScan has scanned is 86 out of 100.

A leaked credential can enable misuse within its permissions, including unauthorized data access or billable API calls. Inspect the credential’s scope and usage when assessing impact.

Why Are Environment Variables a Security Trap?

Environment variables separate configuration from code. Their confidentiality depends on who can access the process, build output, logs, and responses. A value being stored outside the source file does not make it secret from the browser.

Inspect any private API call in browser code. A value loaded from a .env file can still be exposed if the build or application passes it to the client. Use public provider keys only for their documented client use; secret credentials require a protected server-side path.

30% of the 119 live sites SimplyScan has scanned (36 sites) have at least one high or critical security issue.

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

To master environment variable security, you must understand where your code actually executes. This is the foundation of any application security audit.

Server-Side Execution

Server-side runtimes include Node.js backends and deployed functions. They can access private configuration, but application code can still leak it through rendered output, serialized props, responses, logs, or exposed files. Review those paths explicitly.

Client-Side Execution

Browser code can inspect any value delivered to it. Build tools can substitute public configuration values into .js files, and applications can expose values through runtime payloads too. Obfuscation does not turn a client-delivered secret into a private value.

If a privileged credential reaches a public bundle, treat it as exposed even if the application never displays it.

Which Prefixes Make Environment Variables Public?

The following conventions expose public configuration in their respective frameworks. Inspect your actual project and build settings; an editor or app builder does not uniquely determine the framework.

  • Vite: VITE_ (e.g., VITE_STRIPE_KEY) is the default client-exposure prefix; custom build configuration can change exposure.
  • Next.js: NEXT_PUBLIC_ (e.g., NEXT_PUBLIC_ANALYTICS_ID) marks values for supported build-time client references.
  • Create React App: REACT_APP_ (e.g., REACT_APP_MAPBOX_TOKEN) marks public build configuration.
  • Nuxt: NUXT_PUBLIC_ (e.g., NUXT_PUBLIC_BASE_URL) overrides matching public runtime-configuration entries, which are available to the client.

Review Generated Configuration

Review generated .env files alongside the code that reads them. The vibe-coding security checklist can help organize that review; it does not establish a measured breach rate or cause.

Unsafe client-exposed configuration example:

Illustrative server/client split: The public Supabase values below require appropriate database privileges and policies. The private values must be read only by server code and kept out of client output; the absence of a public prefix is not a security guarantee.

How to Avoid Secrets in .env Files (and Git)

Keep secrets only where they are needed and restrict access. A local .env file can be appropriate for development, but inspect what a command such as git add . would stage before committing.

1. Use a .gitignore Template Immediately

Before you even prompt your AI to create a project, initialize your repository and create a .gitignore file. Review the generated ignore rules and inspect tracked files and history separately. Ignore rules do not remove files already committed or prevent secrets from reaching a client build. See gitignore generator.

2. Use Platform Secret Management

Use your hosting or CI platform’s protected configuration for production credentials, with appropriate access controls.

  • Vercel: Configure values for the intended deployment environment.
  • Supabase: The supabase secrets set command configures Edge Function secrets; take care not to expose values in shell history or logs.
  • GitHub: Use Actions secrets for workflows and review which jobs and contributors can access them.

3. The .env.example Pattern

Maintain a .env.example containing variable names and non-secret placeholders. Review its contents before committing; the filename itself does not make a file safe.

Keep Sensitive .env Files Out of Version Control

Inspect tracked files and repository history for sensitive .env files. A .gitignore rule prevents matching untracked files from being added by default; it does not remove tracked files or past commits.

Deleting a file in a later commit leaves earlier versions in history. Copies can also remain in clones, forks, caches, or artifacts. Review these exposure paths as part of a website security audit.

How to Fix a Committed Secret

Revoke exposed secret credentials, create replacements where needed, update deployments, and investigate misuse. Public project identifiers do not require the same response solely because they are visible.

Update .gitignore

Add .env and .env.local to the ignore rules. Check that sensitive .env files are not already tracked and that no public asset directory serves a .env file.

Purge History

Use a tool such as git filter-repo or BFG Repo Cleaner to rewrite affected history after revocation. Coordinate with collaborators and review remote branches, tags, and artifacts; rewriting one repository cannot erase every copy.

Scan

Scan the deployed bundle and repository for detectable secret patterns. Investigate findings and revoke exposed credentials; a clean result does not prove that no secrets remain. See secret scanner.

Framework-Specific Security: Next.js, Supabase, and Vite

Next.js and Vercel

Non-public values are available to server-side code, including getStaticProps, getServerSideProps, and Server Components. The NEXT_PUBLIC_ prefix enables supported build-time client references. A private value is not normally injected into browser code; depending on the access pattern and build, an unsupported reference may be undefined or otherwise unavailable. Server code can still disclose values through props, HTML, or responses. See the Next.js security guide.

Supabase and Lovable

A frontend using VITE_SUPABASE_ANON_KEY uses a credential intended for public client access. Review table privileges and RLS policies, and test allowed and denied operations. Keep SUPABASE_SERVICE_ROLE_KEY and any equivalent SERVICE_ROLE_KEY server-side; never expose them through a VITE_ variable. They can be used in a trusted backend, including Supabase Edge Functions through Deno.env.get(), with carefully authorized operations. See the Supabase security checklist.

Windsurf and Cursor

Review generated .env files, fallback values, and deployment configuration. The SimplyScan MCP server can scan selected file contents with a Pro API key: source is sent to the service for in-memory analysis, masked findings are saved, and no AI provider receives those files. JavaScript and TypeScript receive code-rule checks; other files receive secret-pattern checks. This does not establish that the whole repository is safe.

Configuration Mistakes to Check

1. Hardcoded Fallbacks

This example illustrates a hardcoded fallback:

A real secret used as a fallback can be exposed in the shipped code even when the environment value is present. Inspect the built output and use the web security checklist to organize further tests.

2. Debug Logging

Calls such as console.log(process.env) or console.log(import.meta.env) can reveal configuration available in that runtime. Browser logs expose client values; server logs can disclose private values to people or systems with log access. Avoid logging secret values in either environment.

3. Client-Side Logic for Secrets

Enforce authorization on the backend. This client-side example controls only local UI state:

A client-delivered VITE_ADMIN_CODE is visible to the user, who can also modify local state. If the backend relies on that state, authorization is broken. Validate identity and permission on each protected server operation; see broken access control.

Environment Variable Review Checklist

Review these configuration and exposure paths before deployment:

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

Use git log -p .env as one history check. It is not exhaustive across renamed files, other secret files, every branch, or external copies. Avoid printing secret values into shared logs.

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

Build locally with your project’s command, such as npm run build, and inspect the client-served artifacts. Output may be under dist or .next, but a server-only artifact is not automatically public. Review which files the host actually serves.

Platform Check

Review who can read, change, or deploy configuration values. Provider sensitivity settings can limit dashboard access; they do not prevent application code from leaking a value.

Rotation Check

Revoke exposed credentials promptly and follow provider or organizational rotation requirements for other keys. Verify that replacements are deployed and old credentials are disabled.

Scope Check

Are your development keys different from your production keys?

Automated Scan

Use a URL security scanner for supported exposed-key and public-file patterns. Inspect findings and test other exposure paths separately.

How to Inspect Deployed Secret Handling

Combine browser inspection with repository review and server-side tests. A negative search result does not establish that no leak exists.

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 or an Edge Function.

2. Search the Source Tab

Use browser DevTools to inspect client-served sources. Search shortcuts vary by browser and version; check the commands for Ctrl+Shift+F or Cmd+Option+F in your environment. A prefix such as sk-proj is a lead to inspect, not proof of a valid secret. A real secret in a publicly served .js file is exposed.

3. Use SimplyScan

SimplyScan’s free URL scan typically takes about 30 seconds and checks selected exposed-secret, security-header, and public-file signals. It does not inspect every code path. A GitHub Advanced Security comparison discusses a separate source-scanning workflow.

Hapi.js and Backend Security

If you are using a Hapi.js backend, ensure you are using the dotenv package correctly and not exposing the server.settings.app object if it contains environment variables. Always validate your environment variables on startup to ensure required secrets are present but not leaked through error messages. For more on backend safety, see our guide on Hapi.js injection prevention.

Key Takeaways for Vibe Coders

Review secret handling from configuration through runtime output. A .env filename or the absence of a VITE_ prefix does not establish confidentiality. Keep privileged values server-side and investigate any exposure.

The free URL scan covers selected checks across 8 categories, including observable compliance signals. Those checks do not determine legal compliance or prove that every secret is protected.

Related Guides

Related Free Tools

  • Exposed Files Scanner · Probe selected public paths, including .env and .git paths.
  • API Key Generator · Generate random values for your own application; it does not issue credentials for third-party providers.
  • .env File Linter · Inspect supplied text for supported configuration and secret patterns.
  • Security Headers Checker · Inspect response headers; it does not establish protection against every injection or data leak.

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

The Supabase anon key is intended for client use, including a variable such as VITE_SUPABASE_ANON_KEY. Its effective access depends on table privileges, the request role, RLS state, and applicable policies. Test permitted and denied operations. Keep service-role credentials server-side because their role can bypass RLS.

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

Revoke exposed secret credentials, replace them in deployments, and investigate misuse. Add sensitive .env files to .gitignore and coordinate any repository-history cleanup with collaborators. History rewriting and force pushes do not erase all clones or caches; deleting the current file alone does not revoke the credential.

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

Inspect the client-served sources and network responses. A command such as grep -r "your-secret-key" dist/ illustrates searching built files, but typing real secrets into a command can expose them through shell history or logs. Use a controlled inspection process. If a real secret reaches public JavaScript, revoke it and repair the code path, not only its prefix.

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

A .env file stores configuration values, which may include secrets. A .env.example should contain names and non-secret placeholders, such as OPENAI_API_KEY=your_key_here. Inspect both before committing; a template filename does not prevent accidental disclosure.

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

Use separate, appropriately scoped development and production credentials to reduce the effect of a development leak. Separation is ineffective if the development key still grants production access. Review each deployment environment and test its effective permissions.

Can environment variables leak through console.log?

Yes. A console.log call can disclose any value available to that runtime. Logging process.env on a server can expose server configuration to log readers; logging import.meta.env in a browser can reveal client configuration. Avoid logging secrets in either case.

Frequently asked questions

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

The Supabase anon key is intended for client use, including a variable such as VITE_SUPABASE_ANON_KEY. Its effective access depends on table privileges, the request role, RLS state, and applicable policies. Test permitted and denied operations. Keep service-role credentials server-side because their role can bypass RLS.

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

Revoke exposed secret credentials, replace them in deployments, and investigate misuse. Add sensitive .env files to .gitignore and coordinate any repository-history cleanup with collaborators. History rewriting and force pushes do not erase all clones or caches; deleting the current file alone does not revoke the credential.

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

Inspect the client-served sources and network responses. A command such as grep -r "your-secret-key" dist/ illustrates searching built files, but typing real secrets into a command can expose them through shell history or logs. Use a controlled inspection process. If a real secret reaches public JavaScript, revoke it and repair the code path, not only its prefix.

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

A .env file stores configuration values, which may include secrets. A .env.example should contain names and non-secret placeholders, such as OPENAI_API_KEY=your_key_here. Inspect both before committing; a template filename does not prevent accidental disclosure.

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

Use separate, appropriately scoped development and production credentials to reduce the effect of a development leak. Separation is ineffective if the development key still grants production access. Review each deployment environment and test its effective permissions.

Can environment variables leak through console.log?

Yes. A console.log call can disclose any value available to that runtime. Logging process.env on a server can expose server configuration to log readers; logging import.meta.env in a browser can reveal client configuration. Avoid logging secrets in either case.

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.
  • Replit Login & Auth Security: How to Use Secrets to Prevent Key Leaks · Keep application credentials in server-side secret storage, validate authenticated sessions, and enforce permissions on protected operations. Replit Auth and Secrets serve different roles. Review generated code and deployed resources, and investigate supported scanner findings alongside tests of your application’s access controls.
  • 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