vite-plugin-safe-env
v1.1.0
Published
A Vite plugin that prevents server-only environment variables from leaking into client-side bundles.
Downloads
165
Maintainers
Readme
Vite ensures only VITE_-prefixed variables are exposed through import.meta.env, but offers no such protection for process.env (exposed by define: { 'process.env': loadEnv(...) }). A server-side module accidentally imported into the client entry tree can silently inline credentials like DATABASE_URL or JWT_SECRET into the production bundle, readable by anyone who downloads the JavaScript file.
vite-plugin-safe-env catches this class of bug through two detection phases:
Phase 1 (Static analysis): Scans every source module for process.env.X and import.meta.env.X accesses, walks the module graph to find which of them are reachable from client entry points, and reports violations with the exact file, line, and import chain.
Phase 2 (Bundle scan): After the bundle is produced, scans every output chunk for the literal string values of known server-only variables. This is the ground-truth gate. If a secret's value appears in the output, the build fails.
Why this matters
The risk is documented and has affected real production systems.
Sprocket Security (2024): AWS credentials and CircleCI API keys were found as plain strings in a production Vite bundle. A server-side module had been accidentally imported from the client entry tree. The exposed keys granted full access to the CI/CD pipeline, all stored secrets, and every connected repository.
Vite issue #17710: Confirmed bug where referencing a non-existent env variable causes Vite to inline the entire process.env object into the client bundle, exposing every secret available in the build environment.
Installation
npm install -D vite-plugin-safe-envpnpm add -D vite-plugin-safe-envyarn add -D vite-plugin-safe-envUsage
Zero configuration required.
// vite.config.ts
import { defineConfig } from 'vite'
import safeEnv from 'vite-plugin-safe-env'
export default defineConfig({
plugins: [safeEnv()],
})How it works
What gets flagged
Any environment variable accessed without a client-exposed prefix (your envPrefix configuration, or VITE_ by default) that is reachable from a client entry point:
// leaking-usage.ts (imported from a client entry)
export const dbUrl = process.env.DATABASE_URL // flagged
export const dbUrl2 = import.meta.env.DATABASE_URL // also flagged
export const apiUrl = import.meta.env.VITE_API_URL // safe, has VITE_ prefiximport.meta.env.DATABASE_URL is flagged even though Vite resolves it to undefined: the access is a silent bug, missing at runtime with no build-time error. To use a variable on the client, give it a prefix Vite exposes (your envPrefix, VITE_ by default) or add it to allowClientAccess.
Error output
[vite-plugin-safe-env] Server-only environment variable may leak to client bundle
Variable DATABASE_URL
File src/utils/db.ts:3
Reachable via
src/main.tsx
src/components/UserCard.tsx
src/utils/db.ts
Risk
The value of DATABASE_URL will be visible in your production
JavaScript bundle to anyone who opens browser DevTools.
Fix
DATABASE_URL appears to be a credential. Exposing it in the client bundle
is a security vulnerability. Move all access into a server-only module that
is never imported from a client entry point.In development mode, violations appear as a browser overlay using the same style as Vite's built-in TypeScript error overlay.
Known limitation
Phase 1 only detects named accesses like process.env.DATABASE_URL. Computed names like process.env[key] are invisible to static analysis. Phase 2 compensates by scanning the compiled bundle for the actual string values of server variables.
Configuration
All options are optional. The plugin is non-intrusive by default.
safeEnv({
// Variables explicitly permitted on the client side, even without the VITE_ prefix.
// Default: []
allowClientAccess: ['NODE_ENV', 'BUILD_SHA'],
// When to abort the build on a detected leak.
// 'production' aborts only when NODE_ENV is production (default)
// 'always' aborts on every build
// 'never' reports violations but never aborts
blockOn: 'production',
// Glob patterns for source files to scan.
// Default: ['**/*.{ts,tsx,js,jsx,vue,svelte}']
include: ['src/**/*.ts'],
// Glob patterns for source files to skip. Takes precedence over include.
// Default: ['node_modules/**', 'dist/**']
exclude: ['src/generated/**'],
// Show the browser overlay in development mode.
// Default: true
overlay: true,
})Examples
Allow specific non-prefixed variables
Some projects use variables like NODE_ENV or CI on the client intentionally:
safeEnv({
allowClientAccess: ['NODE_ENV', 'CI', 'BUILD_TIMESTAMP'],
})Strict mode
Fails the build regardless of environment. Useful in CI pipelines that run against staging:
safeEnv({ blockOn: 'always' })Warning-only mode for gradual adoption
Reports all violations to the terminal without blocking the build. Useful when adding the plugin to an existing codebase with many violations:
safeEnv({ blockOn: 'never' })License
MIT License © 2026-present Cong Nguyen
