Environment-secret exposure through webpack DefinePlugin

Environment-secret exposure through webpack DefinePlugin

Description

Defining all of process.env as a serialized replacement in webpack's DefinePlugin can embed secrets in a client bundle when those references are replaced. The emitted values depend on the referencing code and optimization, but the entire build environment should not be treated as public data. Anyone able to download the JavaScript bundle or source maps may recover exposed API keys, tokens or other secrets.

Potential impact

  • Credentials or API keys included in public artifacts may be recovered.
  • Exposed credentials may enable unauthorized backend API calls or data access.
  • Third-party service keys may be abused, increasing traffic or charges.
  • Exposure may breach privacy or security requirements and damage trust.

Remediation

  • Define only explicitly approved public variables, rather than passing the entire environment to DefinePlugin.
  • Keep server-only secrets out of client code. A naming convention such as PUBLIC_ can help distinguish approved variables, but does not protect their values.
  • Separate environment files by purpose and supply only the required public variables to client builds in CI/CD.
  • Define individual keys or use an explicit list such as EnvironmentPlugin(['NODE_ENV', 'PUBLIC_API_BASE']).

Examples

Before

javascript
// webpack.config.js (before)
const { DefinePlugin } = require("webpack");

// Replacing the entire environment may embed secrets in referencing code
const exposeAll = { "process.env": JSON.stringify(process.env) };

module.exports = {
  mode: "production",
  plugins: [new DefinePlugin(exposeAll)],
};

After

javascript
// webpack.config.js (after)
const { DefinePlugin, EnvironmentPlugin } = require("webpack");
require("dotenv").config();

// Expose only explicitly approved public keys, for example PUBLIC_ values
const PUBLIC_VARS = {
  "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
  "process.env.APP_VERSION": JSON.stringify(require("./package.json").version),
  "process.env.PUBLIC_API_BASE": JSON.stringify(process.env.PUBLIC_API_BASE),
};

module.exports = {
  mode: "production",
  plugins: [
    new DefinePlugin(PUBLIC_VARS),
    // Or explicitly allow only the required keys
    // new EnvironmentPlugin(['NODE_ENV', 'PUBLIC_API_BASE'])
  ],
};

Explanation:

  • Before: Serializing the entire environment creates a replacement that can leave server secrets in public JavaScript or source maps.
  • After: Only approved variables are defined individually. Verify that every selected value is actually public, and check other bundling and source-map paths; a PUBLIC_ prefix alone does not prevent disclosure.

References