Safeguard
Open Source

dotenv-webpack: How to Keep Secrets Out of Your Bundle

dotenv-webpack loads .env values into a webpack build, but it inlines them into client code at compile time, so anything you reference ships to the browser in plaintext.

Karan Patel
Platform Engineer
5 min read

dotenv-webpack is a webpack plugin that reads a .env file and substitutes process.env.* references in your code at build time, and its most important security property is that it only ships the variables your code actually references — not the whole file. That single behavior is why it exists, and misunderstanding it is how teams accidentally leak API keys into a client bundle.

If you have used the plugin, you have written something like new Dotenv() in your webpack config and then read process.env.API_URL in your app. What is actually happening under the hood matters, because it decides whether a secret ends up in JavaScript that any visitor can download and read.

What dotenv-webpack actually does

The plugin wraps two things: the dotenv library, which parses a .env file into key-value pairs, and webpack's built-in DefinePlugin, which does a literal text replacement across your bundle. When webpack compiles, every occurrence of process.env.SOME_VAR in your source is replaced with the literal value from .env.

The word people latch onto in the project description is "secure," and it has a specific meaning. Unlike dumping the whole environment into the build, webpack dotenv setups using this plugin only inline the variables you explicitly reference in code. A .env value that is never mentioned as process.env.NAME anywhere in your source does not appear in the output bundle at all. That is a genuine and useful guard against fat-fingering an entire secrets file into a browser download.

A minimal config:

const Dotenv = require('dotenv-webpack');

module.exports = {
  plugins: [
    new Dotenv({
      path: './.env',
      safe: true,        // require a .env.example to match against
      systemvars: true,  // also pull in real environment variables
    }),
  ],
};

The trap: inlining is not encryption

Here is the part that bites people. "Only the referenced variables" is not the same as "safe to put secrets in." If your frontend code references process.env.STRIPE_SECRET_KEY, that secret is now baked, in plaintext, into a JavaScript file served to every browser. There is no runtime lookup and no server boundary. It is a compile-time string replacement, and the string is your secret.

The project's own guidance is blunt about this: environment variables are stored in plaintext and are visible to anyone who can read the bundle, so genuinely sensitive material needs a real secrets manager and should never be referenced from client-side code in the first place.

The rule of thumb: dotenv-webpack is fine for build-time configuration that is safe to be public — a public API base URL, a feature flag, a publishable (not secret) key. It is the wrong tool for anything that grants access on its own. Server secrets belong on the server, behind an API, never in a browser bundle.

A known footgun: no destructuring

Because the plugin relies on DefinePlugin's literal text replacement, destructuring breaks it. This does not work as people expect:

const { API_URL } = process.env;   // NOT replaced correctly

DefinePlugin matches the full process.env.API_URL token, so you have to reference each variable by its complete path:

const apiUrl = process.env.API_URL;  // works

This is documented behavior, not a bug, but it is a frequent source of "why is my variable undefined" confusion.

Verifying what actually shipped

Never trust that the plugin did the right thing — check the output. The reliable move is to inspect the compiled bundle:

npx webpack --mode production
grep -R "sk_live" dist/    # search the built output for a secret prefix

If a secret string shows up in dist/, it is exposed, full stop. Tools like webpack-bundle-analyzer help you see what made it into the bundle, and a quick grep for known key prefixes (sk_, AKIA, ghp_) is a cheap CI gate. Catching a leaked key at build time is far better than rotating it after it shows up in someone's browser dev tools.

Where this fits in supply-chain hygiene

dotenv-webpack is a build-time dependency, which means it and its own transitive dependencies run with access to your .env file and your build environment. A compromised or typosquatted build plugin is a direct path to exfiltrating exactly the secrets you are trying to protect. Keeping build tooling pinned, reviewed, and scanned matters as much as scanning runtime dependencies — an SCA tool such as Safeguard can flag known vulnerabilities in your build-chain packages transitively, not just the ones you import directly.

For the broader picture on why build-time dependencies deserve the same scrutiny as runtime ones, our software composition analysis overview walks through the transitive risk model, and the Academy covers secrets handling in CI pipelines.

FAQ

Is it safe to put secrets in a .env file with dotenv-webpack?

Only if those "secrets" are safe to be public. Anything referenced in client code is inlined in plaintext into the browser bundle. Real secrets — server API keys, database credentials, private tokens — must stay server-side behind an API and never be referenced from frontend code.

Why is my dotenv-webpack variable undefined?

The most common cause is destructuring process.env. Because the plugin uses webpack's DefinePlugin for literal text replacement, you must reference the full path like process.env.MY_VAR rather than destructuring it out.

Does dotenv-webpack include my whole .env file in the build?

No. It only inlines variables you explicitly reference as process.env.NAME in your code. Variables present in .env but never referenced are left out of the bundle, which is the plugin's main safety feature.

How do I confirm no secret leaked into the bundle?

Build in production mode and grep the output directory for known secret prefixes, or run webpack-bundle-analyzer. Adding a grep-based secret scan to CI catches accidental exposure before it reaches production.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.