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.
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:
.envfiles are often committed in the versioning system (e.g. git)
.envfiles 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
.envfiles specific permissions like
chmod 600to restrict access
- forbid access in your
.htaccess, nginx config, or Web.Config to all
- 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
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)
Each ENV var has a name and an associated value, and the name will likely be uppercase:
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"
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
It’s pretty cool, but never use such files to store secrets, as many programs will be able to read them.
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.
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
printenvcommand to print ENV vars after exploiting a vulnerability
- 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
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.