fix: ability to use built-in properties in dotenv and define plugin (#20168)

This commit is contained in:
Alexander Akait 2025-11-26 13:28:59 +03:00 committed by GitHub
parent 9525f0a222
commit 5ce84ec8c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 28 additions and 12 deletions

View File

@ -182,7 +182,7 @@ const stringifyObj = (
code = `{${keys
.map((key) => {
const code = obj[key];
return `${JSON.stringify(key)}:${toCode(
return `${key === "__proto__" ? '["__proto__"]' : JSON.stringify(key)}:${toCode(
code,
parser,
valueCacheVersions,
@ -536,7 +536,7 @@ class DefinePlugin {
return;
}
/** @type {Record<string, CodeValue>} */
const obj = {};
const obj = Object.create(null);
const finalSet = finalByNestedKey.get(nested);
for (const { id } of destructed) {
const fullKey = `${nested}.${id}`;

View File

@ -54,7 +54,7 @@ const validate = createSchemaValidation(
* @returns {Env} parsed environment variables object
*/
function parse(src) {
const obj = /** @type {Env} */ ({});
const obj = /** @type {Env} */ (Object.create(null));
// Convert buffer to string
let lines = src.toString();
@ -174,7 +174,7 @@ function expandValue(value, processEnv, runningParsed) {
*/
function expand(options) {
// for use with progressive expansion
const runningParsed = /** @type {Env} */ ({});
const runningParsed = /** @type {Env} */ (Object.create(null));
const processEnv = options.processEnv;
// dotenv.config() ran before this so the assumption is process.env has already been set
@ -183,7 +183,8 @@ function expand(options) {
// short-circuit scenario: process.env was already set prior to the file value
value =
processEnv[key] && processEnv[key] !== value
Object.prototype.hasOwnProperty.call(processEnv, key) &&
processEnv[key] !== value
? /** @type {string} */ (processEnv[key])
: expandValue(value, processEnv, runningParsed);
@ -332,7 +333,7 @@ class DotenvPlugin {
// Parse all files and merge (later files override earlier ones)
// Similar to Vite's implementation
const parsed = /** @type {Env} */ ({});
const parsed = /** @type {Env} */ (Object.create(null));
for (const content of contents) {
if (!content) continue;
@ -422,7 +423,7 @@ class DotenvPlugin {
// Make a copy of process.env so that dotenv-expand doesn't modify global process.env
const processEnv = { ...process.env };
expand({ parsed, processEnv });
const env = /** @type {Env} */ ({});
const env = /** @type {Env} */ (Object.create(null));
// Get all keys from parser and process.env
const keys = [...Object.keys(parsed), ...Object.keys(process.env)];
@ -430,7 +431,11 @@ class DotenvPlugin {
// Prioritize actual env variables from `process.env`, fallback to parsed
for (const key of keys) {
if (prefixes.some((prefix) => key.startsWith(prefix))) {
env[key] = process.env[key] ? process.env[key] : parsed[key];
env[key] =
Object.prototype.hasOwnProperty.call(process.env, key) &&
process.env[key]
? process.env[key]
: parsed[key];
}
}

View File

@ -1,13 +1,22 @@
"use strict";
it("should expose only APP_ and CONFIG_ prefixed vars", () => {
it("should expose only APP_ and CONFIG_ prefixed vars and built-in properties", () => {
expect(process.env.APP_NAME).toBe("MyApp");
expect(process.env.CONFIG_TIMEOUT).toBe("5000");
// WEBPACK_ prefixed should not be exposed
expect(typeof process.env.WEBPACK_API_URL).toBe("undefined");
// Non-prefixed should not be exposed
expect(typeof process.env.SECRET_KEY).toBe("undefined");
expect(process.env.constructor).toBe("test");
expect(process.env.__proto__).toBe("test");
const { __proto__ } = process.env;
expect(__proto__).toBe("test");
const { constructor } = process.env;
expect(constructor).toBe("test");
});

View File

@ -2,3 +2,5 @@ APP_NAME=MyApp
CONFIG_TIMEOUT=5000
WEBPACK_API_URL=should-not-be-exposed
SECRET_KEY=also-hidden
__proto__=test
constructor=test

View File

@ -34,7 +34,7 @@ module.exports = [
entry: "./custom-prefixes.js",
dotenv: {
dir: path.resolve(__dirname, "./prefixes-env"),
prefix: ["APP_", "CONFIG_"]
prefix: ["APP_", "CONFIG_", "__proto__", "constructor"]
}
},
// Test 5: Mode-specific - .env.[mode] overrides