DEV Community

pO0q 🦄
pO0q 🦄

Posted on • Updated on

ENV vars and security

As enhancing deployment is part of the 12-factor principles, many programming languages and frameworks include helpers and external libraries to ease the use of environment variables.

However, some practices can introduce some risks. Let's see how ENV vars work and how to secure their usage.

Loading configuration files

Modern webapps usually require ENV vars. It's not uncommon to use .env files where you can write a series of key/value pairs. Each environment (like staging, dev, or production) gets its own .env with its own values.
Only the keys remain the same across all environments.

N.B.: Note that some environment might also use additional vars, for example, to configure the debug

The app loads the environment from the configuration file, which often contains secrets like API keys and DB credentials. It might be handy, but it has some risks and inconvenient:

  • the .env files are often committed in the versioning system (e.g. git)
  • the .env files are sometimes deployed on a public folder in production without any restriction (I've seen it multiple times)
  • maintaining these files can quickly become tedious

Attackers will enumerate these files at the speed of light, using free tools like Gobuster or Nikto (there are many other scanners available). There are some workarounds:

  • give the .env files specific permissions like chmod 600 to restrict access
  • forbid access in your .htaccess, nginx config, or Web.Config to all .env files
  • store these files in a specific folder that is not publicly accessible

Let's cut to the chase: none of these workarounds is completely safe. Configurations can be modified on purpose or accidentally, resulting in a public disclosure. At least, it would add a thin layer of security, in addition, but it would not be sufficient in the long term.

In the last scenario, the server points the webapp to a specific subfolder like Public/ instead of exposing the whole installation:

│
├── .gitignore
├── Public/
│   ├── style.css
│   └── index.php
│
├── Config/
│   ├── .env
│   └── config.json
│
└── README.md
Enter fullscreen mode Exit fullscreen mode

This above imaginary structure keeps the .env file outside the web root. A more concrete example would be WordPress and its wp-config.php file that contains database credentials.

Alternative approaches such as Bedrock can provide more protection. The framework leverages ENV vars to store information and provides a convenient way to override configurations according to the environment.

Still, it can have major drawbacks:

  • the framework might not let you do whatever you want
  • you might get unwanted side effects and additional bugs when migrating from a classic installation
  • depending on your approach of versioning, some creds can still be in your git repo in plain text (e.g., dev config)

How these ENV vars work?

Getting started

Each ENV var has a name and an associated value, and the name will likely be uppercase:

MY_ENV_VAR="the value"
Enter fullscreen mode Exit fullscreen mode

Technically speaking, you don't have to use uppercase, but it's a naming convention. On a Linux machine, you can print ENV vars with the env command (no option).

These vars are used by many processes (~ programs), and the values can deeply modify their behavior. For example, $PATH, the ENV var that contains... paths, allows using various tools and commands without entering the path to the binary again and again.

Roughly speaking, this is how most systems work, with processes that fork other processes. During these operations, ENV vars are implicitly available.

On your machine, you may use the export command to set a new ENV var:

export MY_ENV_VAR="the value"
Enter fullscreen mode Exit fullscreen mode

How to make ENV vars persistent

The variable is only available for child sessions and will die with the current shell. In other words, if you exit or close the window, your variable is lost.

To create a persistent var, you can use the same export command and save it in the .bash_profile or the .bashrc file.

It's pretty cool, but never use such files to store secrets, as many programs will be able to read them.

ENV vars vs. shell vars

MY_ENV_VAR="the value" and export MY_ENV_VAR="the value" are not the same commands at all. The first one defines a local var.

To transmit this var to every new child process automatically, you must use export, which converts the local var into an ENV var.
Otherwise, it won't be available.

Don't store secrets in ENV vars

Due to the very nature of ENV vars, using them to store secrets is not the best idea:

  • for any given process, all child processes will inherit ENV vars, exposing the secrets to way more processes than necessary
  • the system will log events containing ENV values in plain text (e.g., debugging, error logs, third-party tools)
  • hackers could use the env or printenv command to print ENV vars after exploiting a vulnerability

Good practices and alternatives

  • don't consider all ENV vars are equal
  • don't use the same API keys for all environments
  • be extra vigilant with permissions and accessible secrets, especially in cloud-based architectures
  • use encrypted secrets. For example, GitHub provides very convenient interface to encrypt your secrets and manage them with granularity
  • use CI/CD solutions that abstract ENV vars away and allow you to update values via secured APIs
  • if you use Kubernetes, use the feature called "secrets", but be aware that secrets are stored unencrypted by default

Wrap up

ENV vars are accessible pretty much everywhere, making them particularly convenient for web development, but sometimes exposing projects to severe leaks and escalations.

The alternative solutions are not always the easiest to implement, and you won't solve all security issues just by skipping ENV vars, but storing secrets in plain text is a bad practice.

Top comments (0)