diff --git a/lib/DefinePlugin.js b/lib/DefinePlugin.js index 09e7e11ea..24170ed70 100644 --- a/lib/DefinePlugin.js +++ b/lib/DefinePlugin.js @@ -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} */ - const obj = {}; + const obj = Object.create(null); const finalSet = finalByNestedKey.get(nested); for (const { id } of destructed) { const fullKey = `${nested}.${id}`; diff --git a/lib/DotenvPlugin.js b/lib/DotenvPlugin.js index e623a9fb7..4a0151aca 100644 --- a/lib/DotenvPlugin.js +++ b/lib/DotenvPlugin.js @@ -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]; } } diff --git a/test/configCases/plugins/dotenv-plugin/custom-prefixes.js b/test/configCases/plugins/dotenv-plugin/custom-prefixes.js index 7d662a506..6e909ff16 100644 --- a/test/configCases/plugins/dotenv-plugin/custom-prefixes.js +++ b/test/configCases/plugins/dotenv-plugin/custom-prefixes.js @@ -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"); }); diff --git a/test/configCases/plugins/dotenv-plugin/prefixes-env/.env b/test/configCases/plugins/dotenv-plugin/prefixes-env/.env index 2bd4be494..9b29dc573 100644 --- a/test/configCases/plugins/dotenv-plugin/prefixes-env/.env +++ b/test/configCases/plugins/dotenv-plugin/prefixes-env/.env @@ -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 diff --git a/test/configCases/plugins/dotenv-plugin/webpack.config.js b/test/configCases/plugins/dotenv-plugin/webpack.config.js index 38a176cd5..d01fe8077 100644 --- a/test/configCases/plugins/dotenv-plugin/webpack.config.js +++ b/test/configCases/plugins/dotenv-plugin/webpack.config.js @@ -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