DEV Community

Cover image for Please don't commit .env
Basti Ortiz
Basti Ortiz

Posted on • Updated on

Please don't commit .env

Let's face it. .env files are amazing. They have an easy-to-read syntax that stores all of our essential configurations into one file. Not only that, they keep our deepest, darkest secrets as web developers. They make sure that our precious API keys and database passwords are kept locally, away from prying eyes. Having such a critical role in our code bases, we are constantly reminded by the community to never share our .env files; to treat them like how the government treats their confidential information.

This is especially enforced in the open-source community where everyone shares, copies, and reuses code with each other. Accidentally committing and pushing the .env file is considered by many as a relatable moment. Personally, I have never done it myself yet, but I'm sure my fate is sealed at this point.

As fun as it is to talk about "that one time you committed the goods" in a casual conversation between developers in a party, it is pretty alarming that it has become a common conversation—perhaps even a rite of passage—in web development.

A stain in the commit history

Running a quick search on GitHub reveals that there are still a number of people who didn't get the memo. The occasional add .env and remove .env commit titles appear every now and then in the search results. Looking into the content of the commits indeed shows their precious API keys and database passwords. It's honestly funny to see how they revert their changes like how a child becomes guilty of doing something they shouldn't have.

What's more alarming about this is that there are still some others who have not reverted their commits. The .env file is still alive and well in their repositories. For all we know, these might be real, actual API keys and database passwords they currently depend on in a regular basis. To make matters worse, sorting the search results by recently committed shows how common and frequent these commits are.

The problem with simply removing the .env file in the working tree is the fact that Git keeps a record of all the commits made in a repository, even the earliest ones. Unless clever tricks have been made (more on that later), committing that .env file will forever be a stain in the commit history. This is just what a version control system is supposed to do after all: keep a history of changes, even the bad ones.

Having said that, how does one handle sensitive data in a repository?

.gitignore is your best friend

Adding a .gitignore file to your repository is your first line of defense against these hiccups. Properly and explicitly specifying what files and directories to ignore is a surefire way of preventing sensitive data from ever reaching public repositories and prying eyes.

GitHub and gitignore.io provide general-purpose .gitignore templates for specific languages and environments. Most of the time, these templates are more than enough to suit your needs.

In the case of already having committed sensitive data in a repository, GitHub has a handy, and rather overkill, guide on how to purge a file from the commit history. "Overkill" is not a bad thing, though. One can never be too safe when it comes to security. 😉

We all make mistakes

We're all humans here. Nobody is perfect. Mistakes are just a part of life. In fact, it is probably one of the most important realities we have to face everyday. Without facing these mistakes, we can never become better developers. A huge part of learning isn't in the affirmation of success but in the recognition of the "whats" and "whys" of failure.

Sure, committing the .env file is a grave mistake. Sure, it will pose serious security risks in your app. Sure, the business will suffer significant losses. Sure, it will be a hassle to clean up the commit history. Sure, it will be hard to sleep at night knowing that you have been compromised. But if there's one good thing that comes out of this experience, it has to be the fact that you will come out as a better, more experienced developer in the end.

With that said, here's a toast to all the future projects that you will never commit the .env file to. Cheers! 🥂 *clink* I shall conclude this article with a parting thought and a final reminder to you, the reader:

Please don't commit .env. You wouldn't want to end up at the top of GitHub's search results.

Latest comments (76)

Collapse
 
yxw007 profile image
Potter

When users access the front-end project, they can naturally capture all the content, so how can the front-end project prevent API key exposure? Is there no way?

Collapse
 
somedood profile image
Basti Ortiz

The most effective way to combat this is to just never embed API keys in the front end. Instead, we use API calls (e.g., fetch) so that some back-end server somewhere performs the API request on behalf of the front end. The API keys thus remain secret in the back end. This is an unfortunately cumbersome but necessary measure.

Collapse
 
yxw007 profile image
Potter

If so, access the API encryption interface through the fetch reappropriate back-end interface. So the API interface encryption on and from the front and back ends is meaningless.

Thread Thread
 
somedood profile image
Basti Ortiz

What do you mean by encryption, by the way? Are you referring to API keys that are embedded inside JWTs? Or are you referring to the general pattern of encrypting API secrets before sending to the front end?

Thread Thread
 
yxw007 profile image
Potter

general pattern of encrypting API secrets before sending them to the front end

Thread Thread
 
somedood profile image
Basti Ortiz

I suppose that is one possible way. Personally, though, I would still prefer just having a centralized server that acts as a proxy for privileged API calls.

Collapse
 
joelbonetr profile image
JoelBonetR 🥇 • Edited

Just to clarity all this text above, which is misleading, the issue is not to commit the .env file!

The issue is storing sensitive data in plain text -be it .env or any other kind of file- inside the repository, either be public (as an obvious security measure) or private (as a double security measure).

It's perfectly fine to have a .env that stores your DB user, password and model if that's meant to point to a local database in your computer (Docker-ized or not) for development purposes or an API key that's meant to work just in local environment within your app cluster. You can then override these values with the production -or any other public environment- ones in the pipeline with the sensitive data values stored in a vault or repository variables.

Best regards

Collapse
 
somedood profile image
Basti Ortiz

The issue is storing sensitive data in plain text—be it .env or any other kind of file—inside the repository, either be public (as an obvious security measure) or private (as a double security measure).

I totally agree with this. I should have been clearer in emphasizing this point. Of course, hindsight is 20-20 five years later (when this article was first published). 😅

It's perfectly fine to have a .env... if that's meant to point to a local database in your computer (Docker-ized or not) for development purposes or an API key that's meant to work just in local environment within your app cluster. You can then override these values [in production]...

This is actually my current stance on the subject nowadays. However, strictly out of abundance of caution, I still avoid committing an .env file into any of my repositories. It's more about the fact that I know I will be working with other people, so I just outright .gitignore potentially sensitive files from the get-go. It is not to say that committing .env is inherently evil, but proactive measures are still better than reactive measures when it comes to computer security.

Collapse
 
joelbonetr profile image
JoelBonetR 🥇

true! 😁

Collapse
 
rman profile image
Arman

Got a question probably a dumb one :D. Let's say the configs in our .env are just for a client side application like react and it includes api keys and such (no database or backend user pass stuff). Why would we care about committing since after the build all the keys are going to be somewhere inside the built file as well? and its visible through the browser when user is working with the website?

Collapse
 
somedood profile image
Basti Ortiz

First and foremost, that's not a dumb question! 😂
You are correct that it shouldn't really matter if the environment variables are truly meant to be deployed with the file bundles.
However, the real issue here is the fact that sensitive keys are publicized in the first place. Needless to say, this is not exactly a secure deployment strategy. Even if the bundles are minified and such, this is a potential attack surface nonetheless.
API keys must be stored and indirectly served via some in-house server-side API, never directly through the client-side code. Potential security risks include (but are not limited to) denial-of-service attacks, impersonation, and backdoor access to app internals. That's no fun!
Though, if the environment variables only include non-sensitive static build configurations and such (i.e. theming options, CSS variables, etc.), perhaps it may be alright to publicize them. Otherwise, you should be extra wary about this deployment strategy—if not reconsider it altogether.

Collapse
 
ajinkyax profile image
Ajinkya Borade

I need the .env file during build on CI how do I make sure GCP receives my .env file if I have not committed it on github ?

  1. I can use Github /settings/secrets
  2. I can pass them to .env file
  3. But how do I read the (/settings/secrets) same in local setup ?
Collapse
 
somedood profile image
Basti Ortiz

To be honest, I am not exactly versed in the Google Cloud Platform. However, I do know they provide a (paid) Secrets Manager service that does exactly what you need in a secure manner. Otherwise, there doesn't seem to be a "free" workaround.

But that doesn't mean other cloud platforms have this limitation. Heroku, for example, allows you to directly set environment variables in the app itself.

So at the moment, I can't offer you any solutions with GCP. I'm unfortunately not familiar with it.

Collapse
 
ajinkyax profile image
Ajinkya Borade

cool. thanks

 
somedood profile image
Basti Ortiz

No problem, Rajesh! Thanks for reading the article! 😉

 
somedood profile image
Basti Ortiz

Yup, pretty much! The best part about adding a sample file is the fact that it can also serve as documentation for environment variables. One can simply annotate and comment on the sample file.

Collapse
 
somedood profile image
Basti Ortiz • Edited

Hm, I would recommend adding an .env.sample file in the repository. Instead of filling in the fields with real values, you can add placeholders, redactions, and "pseudo-values".

Then, of course, this would have to be documented and explained in the appropriate README.md.

Or perhaps, you could even forgo the .env.sample file and declare the environment variables in the README.md itself, but I personally prefer the former because it is more explicit.

# .env.sample
NODE_ENV=<production|development>
PORT=<open-port>
API_KEY=<github-key>

# And so on and so forth...
Enter fullscreen mode Exit fullscreen mode
Collapse
 
sobolevn profile image
Nikita Sobolev

And you can also use dotenv-linter to lint your .env files for consistency and best practices: github.com/wemake-services/dotenv-...

Collapse
 
ondrejs profile image
Ondrej

Here's a relevant article.

Collapse
 
somedood profile image
Basti Ortiz

This is really intricate and interesting. Thanks for sharing! It was a great read (yet very disturbing 😬 from a security standpoint).

Collapse
 
jamezrin profile image
Jaime Martínez Rincón

As if it was fault of Mailgun... It's your friends fault and yours for giving him these credentials.

Collapse
 
luk707 profile image
Luke Harris

Take a look at a package I wrote on NPM: (envup). It allows you to version control the structure of your environment as a separate file making it easy for others to setup their own .env file without commiting any of the data to git.

Collapse
 
gidsg profile image
Gideon Goldberg

Why have it in your git directory at all? Most frameworks let you override the config location so set it to ~/.app-name/.env or similar. If you need to provide an example dummy config you can check in an .env.sample file.

Collapse
 
somedood profile image
Basti Ortiz

Yes, that's true. At least for me, I think it's just easier to have a .env file because it requires minimal setup and messing around with global configurations.

At least in Node.js, all you have to do is npm install and require the dotenv package. Then in your code, just invoke the dotenv.config() and it should all be running smoothly via the process.env object. This way just saves you from the little extra effort you have to do with the nitty-gritty configurations.

But to each its own. Whatever workflow works the best for you, you should apply it, not because everyone does it but because you feel productive with it.

Collapse
 
revskill10 profile image
Truong Hoang Dung

.env is an anti-pattern to me, because it requires overhead to keep it secure.

Collapse
 
somedood profile image
Basti Ortiz

It seems to be quite a popular anti-pattern nowadays. 😅

Besides, adding one line to the .gitignore file shouldn't be that much of an overhead.

Collapse
 
acroyear profile image
Joe Shelby

Oh, and this doesn't just apply to auth and application keys from cloud providers. It can also apply to the credentials you use for demo and testing servers for your client-side app, and database credentials for server-side apps.

Collapse
 
acroyear profile image
Joe Shelby

I noted this a few years ago, before ".env" became the norm, but the effect was the same. People were putting their OAuth keys into configuration files, committing them to git, and a bot search came across several thousand exposed keys just looking for Amazon's. Similar numbers probably would have been found with google and others.

In that case, I wanted to make sure that my users knew where to put their stuff, so I created a credentials.template file that showed the format, and that got committed, but my own credentials did not. One could do the same here by having a README.env.txt file to document what to do, and cat that file to the console in an npm post-install hook.

The negative of that, though necessary, is it means you're not distributing running code. They can't just pull your files down and npm start and everything works. They have to finish the init by creating their own files. It may also complicate automated testing systems that would have to be configured to provide that file before running.

If you have made this mistake already, one possible way to fix it is to interactive rebase back to the sha that introduced the problem, wipe the file and add the .gitignore line there, and then deal with the merge conflicts as it pushes the rest up if you ever had to touch that file again (either in format or in updating the data in it).

Of course, how much work that is depends on the age of your code (how many commits and how many branches).