View profile

The Case Against Using Environment Variables for Credentials and API Keys

The Case Against Using Environment Variables for Credentials and API Keys
By Mastering JS Weekly • Issue #44 • View online
In the early 2010’s, the 12 Factor App popularized the idea that you should store configuration (API keys, database credentials, etc.) in environment variables. Containers (Docker, etc.) made it easy to package environment variables in your deployments, and so environment variable configs have gone largely unchallenged over the last few years.
There’s some content out there against environment variable configs, but I don’t think it is sufficiently harsh in explaining the security issues and DX issues inherent to environment variable configs. Here’s why:

The event-stream Incident
The biggest issue with environment variables is that, in Node.js, `process.env` is mutable global state. Your database credentials and API keys are stored in easily readable global state, and can be compromised.
For example, the famous event-stream hack of 2018 was caused by a compromised npm package scanning through `process.env` to steal credentials for a Bitcoin wallet. Many JavaScript projects have thousands of upstream dependencies on npm, and all it takes is for one of them to be compromised and swipe all your environment variables.
The community does a good job of guarding against this: the event-stream hack was rapidly discovered and patched. But, as your project grows and becomes a more appealing target, `process.env` should be a cause for concern and something you should consider de-risking. Config files are easier to encrypt and harder to find.
Local Development Tooling
12 Factor App lists the fact that environment variables are a language-agnostic standard as one of the primary reasons to prefer environment variables for configuration. And that is a real benefit: I can’t think of a programming language that doesn’t have built-in support for environment variables.
However, using environment variables punts the complexity from the app to the operations and tooling side. It is easy for apps to read environment variables, but managing environment variables typically requires specialized tooling. In particular, in order to run different apps simultaneously with different environment variables, you need containers or some other tool that sets up environment variables.
For local development using environment variables, I typically have a `local.env` file checked in to GitHub that contains environment variables for local dev: MongoDB pointed to localhost:27017, test API keys, etc. I then use the node-env npm package to read `.env` files (dotenv is a common alternative), and run scripts like this:
This isn’t bad, but it would be easier and less error prone if the app just read the local config by itself.
Implicit Configuration
The npm ecosystem does all sorts of questionable things with environment variables. I remember I lost all respect for the React Native team after I spent hours tracking down a bug that came down to them overwriting NODE_ENV in a very specific case.
The lesson is that there are many npm packages which switch behavior depending on environment variables. For example, Express enables view caching when NODE_ENV === ‘production’. Unless you carefully read the documentation or the code, there’s no way for you to know which npm packages depend on which environment variables.
Suppose you have an HTTP_PROXY environment variable for internal use. You might be surprised to know that Axios reads the HTTP_PROXY environment variable and, by default, uses it for all requests. You might figure that out if you’re using Axios directly and read the docs, but if Axios is an upstream dependency for you, this behavior might come back to bite you.
The takeaway here is that any npm package your app depends on, directly or indirectly, may use an environment variable in a surprising way. Adding a new environment variable may change the behavior of your app because of how an upstream dependency uses that environment variable.
Moving On
Environment variables are a common pattern for configuration. I use them frequently. But I think that storing potentially sensitive credentials and core configuration in mutable global state is a debatable idea. Take this as food for thought.
PS: We recently released Mastering Mongoose, an eBook that explains the fundamentals of building backend services with Mongoose. Check it out!
Most Recent Tutorials
Other Interesting Reads
Use environment variables in the configuration | Metricbeat Reference [7.9] | Elastic
Did you enjoy this issue?
Mastering JS Weekly

A weekly summary of our tutorials

If you don't want these updates anymore, please unsubscribe here.
If you were forwarded this newsletter and you like it, you can subscribe here.
Powered by Revue