dotenv-webpack icon indicating copy to clipboard operation
dotenv-webpack copied to clipboard

Multi-variable destructuring breaks when working with webpack 3

Open evisong opened this issue 6 years ago • 25 comments

Hi, team,

Found an interesting bug:

Issue

Using [email protected] and [email protected],

// .env
DB_HOST=127.0.0.1
DB_PASS=foobar
S3_API=mysecretkey

Use the vars in JS:

const { DB_HOST, DB_PASS } = process.env;

Would cause runtime error:

Uncaught (in promise) ReferenceError: process is not defined

Expected

It should replace both vars at compile time.

Possible Root Cause

I believe this issue is caused by https://github.com/webpack/webpack/issues/5215

So I changed the JS to:

const { DB_HOST } = process.env;
const { DB_PASS } = process.env;

It works well as expected.

I debugged the plugin and found that the underlying formatData (https://github.com/mrsteele/dotenv-webpack/blob/master/src/index.js#L63) transferred to webpack.DefinePlugin is:

{
  'process.env.DB_HOST': '"127.0.0.1"',
  'process.env.DB_PASS': '"foobar"',
  'process.env.S3_API': '"mysecretkey"'
}

However the format that recommended in https://github.com/webpack/webpack/issues/5215#issuecomment-314363436 is:

new DefinePlugin({
  "process.env": {
    a: JSON.stringify("a"),
    b: JSON.stringify("b")
  }
})

So I guess a minor fix in dotenv-webpack would solve the issue.

However, I think it ought to be a webpack.DefinePlugin regression bug, since it the current formatData used to work well with webpack 2.

What do you think? Thanks.

evisong avatar Jul 20 '17 04:07 evisong

@evisong Very interesting find.

A brief history of this plugin: We actually used to do what you have recommended (see PR #42), but by defining a single property as process.env it would bundle ALL environment variables which we decided against because of the fact that it could leak variables you didn't want to expose.

i.e.

env

NOTASECRET=123
SECRET=321

script.js

console.log(process.log.NOTASECRET)

bundle.js

console.log({"NOTASECRET":"123","SECRET":"321"}.NOTASECRET)

While there are many approaches that can solve this with different levels of effort, we opted to steer away from "white-listing" variables and such and just letting you expose what you choose in your code.

Let me do a little more investigating since webpack have changed the bundling mechanics since we last looked at this. Anyone else interested feel free to chime in as well.

mrsteele avatar Jul 20 '17 13:07 mrsteele

I can confirm your suggestion would leak the data, however you are completely right in saying that the current approach does not support destructors due to the bundling methods.

I have added all of my test files to the dotenv-webpack-example repo you can review either below or view the source at https://github.com/mrsteele/dotenv-webpack-example.

Goals

Our goals are consistent between the test cases:

  • Successfully be able to read from process.env using destructors (const { TEST } = process.env)
  • Successfully be able to read from process.env by explicit Reference (process.env.TEST)
  • Should not leak sensitive data (only expose references variables explicitly)

Consistent Test Files

The following files were consistent between the test cases.

env

TEST=Works!
TEST2=Yes!
TEST_SECRET=nope

script.js

const { TEST, TEST2 } = process.env

document.querySelector('body').innerHTML = `
  Structured: ${process.env.TEST}<br />
  Destructured: ${TEST}<br />
  <hr />
  Structured: ${process.env.TEST2}<br />
  Destructured: ${TEST2}<br />
`

@evisong's Suggested Approach

This approach is to pull all the variables to be defined from the environment into a single property process.env and pass that object into the DefinePlugin provided by Webpack.

Goals
  • [x] Successfully be able to read from process.env using destructors (const { TEST } = process.env)
  • [x] Successfully be able to read from process.env by explicit Reference (process.env.TEST)
  • [ ] Should not leak sensitive data (only expose references variables explicitly)
bundle.js
...
var _process$env = Object({"TEST":"Works!","TEST2":"Yes!","TEST_SECRET":"nope"}),
    TEST = _process$env.TEST,
    TEST2 = _process$env.TEST2;


document.querySelector('body').innerHTML = '\n  Structured: ' + "Works!" + '<br />\n  Destructured: ' + TEST + '<br />\n  <hr />\n  Structured: ' + "Yes!" + '<br />\n  Destructured: ' + TEST2 + '<br />\n';
...

Current Approach

The current approach (latest in prod) defines a key-value pair object that prefixes process.env. to all variables so that the explicit variable references get bundled and the neglected variables are not included to prevent exposing unnecessary values.

Goals
  • [ ] Successfully be able to read from process.env using destructors (const { TEST } = process.env)
  • [x] Successfully be able to read from process.env by explicit Reference (process.env.TEST)
  • [x] Should not leak sensitive data (only expose references variables explicitly)
bundle.js
...
var _process$env = process.env,
    TEST = "Works!",
    TEST2 = "Yes!";


document.querySelector('body').innerHTML = '\n  Structured: ' + "Works!" + '<br />\n  Destructured: ' + TEST + '<br />\n  <hr />\n  Structured: ' + "Yes!" + '<br />\n  Destructured: ' + TEST2 + '<br />\n';
...

I'm all ears for suggestions. Of course the suggested workaround would just be to define your variables explicitly since that currently works and doesn't pose a security threat for your application.

mrsteele avatar Jul 20 '17 14:07 mrsteele

@mrsteele your comments is very detailed & professional, thanks, I also look forward to any others' feedback.

Btw, do you think it's a good idea to submit our use case to webpack? The key is that it used to work well with webpack 2.

evisong avatar Jul 21 '17 02:07 evisong

@evisong thanks and I appreciate the feedback.

I think it may be worth mentioning to webpack. We have definitely done the research beforehand and we can use the previous ticket you mentioned above as a guide as well.

The time may be soon approaching where we have to circumvent DefinePlugin and write it ourselves, which I don't particularly look forward to but also recognize that day may be unavoidable.

mrsteele avatar Jul 21 '17 12:07 mrsteele

Happen to find that I was using [email protected] previously with webpack 2, and the 893d0d0d security fix is introduced in 1.4.3. So this issue should also apply to all webpack versions, right?

The current workaround is working well:

const { DB_HOST } = process.env;
const { DB_PASS } = process.env;

evisong avatar Jul 25 '17 02:07 evisong

Workarounds are great, but I think the original issue still stands:

As a user, I need to be able to destruct any and all properties from an object.

mrsteele avatar Jul 25 '17 13:07 mrsteele

This prevents me from doing things like:

console.log(process.env[`${name}`])
const env = window._env_ ? window._env_ : process.env;
console.log(env.NODE_ENV)

33Fraise33 avatar May 28 '18 13:05 33Fraise33

@33Fraise33 I understand the frustration, unfortunately this is a complication in the way webpack.DefinePlugin works, you will see the test-cases I wrote above and pros and cons with switching some of the architecture.

I opted to keep things they way they are for the purposes of security and not accidentally leaking any information out that you don't wanna leak.

mrsteele avatar May 29 '18 13:05 mrsteele

Not sure if webpack allows this, but could it be possible to do a two-pass scan? This way the first one could search for all the environment variables used in the object destructure, and later do the white-list of them...

piranna avatar Sep 10 '18 18:09 piranna

Hey everyone, I wanted to ask if this was still a problem.

I just did a test on my https://github.com/mrsteele/dotenv-webpack-example/commit/947cb5cfda7a30bbfaab3cb1deac73a0d2730817 app to see if this was still a problem and it looks like the latest version of webpack has destructing working just fine.

Can anyone else confirm?

mrsteele avatar Nov 20 '18 19:11 mrsteele

Maybe asking to webpack devs? I would like to know too...

piranna avatar Nov 20 '18 19:11 piranna

It worked for me. It loaded just fine all the variables I referenced and didn't leak anything out so I imagine there is some magic transpiling/tree-shaking going on that resolved this on there end.

@piranna have you tried to upgrade webpack and babel?

mrsteele avatar Nov 20 '18 19:11 mrsteele

My bundle.js still contains var _process$env = process.env, leading to ReferenceError: process is not defined. The actual variables defined using object destructuring are replaced with their values, as expected.

  • dotenv-webpack 1.6.0
  • webpack 4.28.3

Splitting the multi-variable destructuring into separate assignments avoids this problem, as suggested above.

cjolowicz avatar Jan 01 '19 22:01 cjolowicz

I'm using

  • dotenv-webpack 1.7.0
  • webpack 4.31.0

Still can not do:

  const { BASE_URL, API_VERSION } = process.env;

But yeah the wordaround mentioned here works.

kilgarenone avatar May 28 '19 03:05 kilgarenone

I'm using "webpack": "5.20.0" Don't works const { BASE_URL, API_VERSION } = process.env; // undefined undefined

DenisAvilov avatar Feb 04 '21 22:02 DenisAvilov

I just stumbled on the same issue where the line below works:

const { DIRECTUS_ENDPOINT } = process.env;
// You can use `DIRECTUS_ENDPOINT` as expected.

Whereas the line below throws the process is undefined error:

const { DIRECTUS_ENDPOINT, DIRECTUS_ACCESS_TOKEN } = process.env;
// Triggers `process is undefined`

The workaround below does indeed work but doing this mostly defeats the purpose of destructuring:

const { DIRECTUS_ENDPOINT } = process.env;
const { DIRECTUS_ACCESS_TOKEN } = process.env;
// You can use `DIRECTUS_ENDPOINT` and `DIRECTUS_ACCESS_TOKEN` as expected.

Even worse, if you console.log process.env, things work as expected:

const { DIRECTUS_ENDPOINT, DIRECTUS_ACCESS_TOKEN } = process.env;
console.log(process.env);
// You can use `DIRECTUS_ENDPOINT` and `DIRECTUS_ACCESS_TOKEN` as expected.

This seems extremely counter intuitive and confusing from a developer's perspective and I'm sure this has and will continue to trip up developers because of understandable assumptions. If you want to drive a developer crazy, it looks like you found an effective method. If you can access process.env.DIRECTUS_ENDPOINT that way, you can infer that process.env is an object. It seems fair to then infer that destructuring process.env to get to more than one property would work like it would with any object in JavaScript. Not exposing process.env as an object like the Node runtime does seems non-standard. If you're not following the standards of the Node runtime, then maybe have the environment variables live in a global variable that does not have the same name as the one used by Node. This way, developers might be more careful about their assumptions (namely, not assumed that it works just like Node), even if not being able to destructure what looks like an object to get two properties is still extremely confusing, especially if using console.log fixes it.

yvesgurcan avatar Jun 28 '21 07:06 yvesgurcan

I think we can close this

and focus on webpack 5

sibelius avatar Oct 13 '21 13:10 sibelius

Can there be any fix for this issue in Webpack 5?

piranna avatar Oct 13 '21 13:10 piranna

I have noticed this works intermittently in webpack 5. I think some of the gatchas mentioned above are the reasonings. I do know with webpack 5 we have to be careful about the "target" property from webpack to make sure we are targeting a web environment when we build.

Since this plugin uses webpack.DefinePlugin under the hood, it would be on that system to resolve that issue.

Alternatively, we could look at using something other than process.env to write to, but you would lose some of the seamless integration aspect of this plugin.

new Dotenv({
  envVarName: 'process.env' // The default, but maybe we could configure it? Perhaps there is too much complexity for DefinePlugin to read from this variable...
})

mrsteele avatar Oct 13 '21 15:10 mrsteele

I do not recommend destructuring

sibelius avatar Oct 13 '21 15:10 sibelius

I do not recommend destructuring

@sibelius what do you propose?

piranna avatar Oct 13 '21 17:10 piranna

create a config.ts file that has all your process.env like this

export const config = {
    API_URL: process.env.API_URL,
};

always consume process.env from that file, so you can destructure if needed

sibelius avatar Oct 13 '21 17:10 sibelius

Anyway to make the destructuring?

tutods avatar Apr 14 '22 09:04 tutods

Anyway to make the destructuring?

I had used it in webpack 5+ but found it didn't work still.

I think they don't think the destructuring is a good idea at all. So...As you can see anyone give up for using the function anymore.

cjfff avatar Oct 05 '23 02:10 cjfff

Ve el siguiente link: https://medium.com/@desinaoluseun/using-env-to-store-environment-variables-in-angular-20c15c7c0e6a

mgardunoH avatar Feb 16 '24 20:02 mgardunoH