Global Env in Node.js: Safeguarding Your Secrets
The Onboardbase team is excited to announce the release of Global Env, an npm package to help developers securely manage environment variables in Node.js applications. Many thanks to the project team and contributors for their hard work!
We are actively working on rolling out more open-source software (OSS) tools and libraries to help the DevSecOps community, so don’t hesitate to reach out to Dante, Founder of Onboardbase.
Read on to learn more about using global env
in your NodeJS project: we explain the problems we are trying to solve as well as concrete steps and examples to use the package in your own development workflow.
The Problem It Solves
global env addresses the issue of unintentional secrets exposure by environment variables in NodeJS programs .
Developers rely on the process.env
variable to access environment variables directly. This approach brings a number of security risks: the environment variables are available throughout the entire runtime of the application, unencrypted, and potentially displayed in logs and other debugging tools. Not to mention the possibility of leaking .env files in git repositories or in publicly accessible folders.
Instead, global env provides a secure way to dynamically access environment variables: the secrets are only available when needed and never persisted anywhere. And the best part is that it’s easy to use and doesn’t disrupt the way you already fetched environment variables!
Before, your code would look like this:
const apiKey = process.env.API_KEY;
The API key is directly fetched from process.env
, which is prone to accidental exposure. You’ll need to define the environment variable in a .env file or in your CI/CD pipeline, and a bad console.log
call could leak the secret.
Using global env, your code would become:
const apiKey = env("API_KEY")
In the step-by-step guide, we’ll show you how global env
’s internal API allows you to securely fetch environment variables dynamically at runtime from local files or remote sources.
Step-by-Step Guide
1. Installation
Install the package in your NodeJS project:
npm i @onboardbase/global-env
global env
is 100% open-source on Github.
2. Setting Up a Secret Store (In-Memory)
global env
uses a setupGlobalEnv.ts
file to configure how the env
function sets and retrieves environment variables. This file is placed in the root folder of your project and imported at the top of the root file of your project:
import "./setupGlobalEnv";
To quickly get started in a local environment, you can store environment variables in-memory. This is especially useful for environment variables that aren’t sensitive, like the port number of your development server:
./src/setupGlobalEnv.ts
import {
setupStore,
memoryStore,
allEnv
} from "@onboardbase/global-env"
const IN_MEMORY_SECRETS = {
SECRET_KEY: "MOHBAD"
}
memoryStore.getSecrets = async () => {
return new Promise((resolve, reject) => {
resolve(IN_MEMORY_SECRETS)
})
}
memoryStore.setSecret = async (key, value) => {
let secrets = allEnv()
secrets[key] = value
}
(async function () {
await setupStore(memoryStore)
})()
The setupStore
will run whenever the application starts and will configure the env
function to use the memoryStore
to fetch and store secrets.
The memoryStore
object presents two required methods for getting and setting secrets.
Note that global env
is compatible with Typescript. You can use the SECRET_KEY and SECRET_VALUE types to ensure that your secrets are properly typed:
import { SECRET_KEY, SECRET_VALUE } from "@onboardbase/global-env"
3. Secret Store from a Remote Source
The real power of global env
comes from its ability to fetch secrets from a remote source. All you need to do is update the getSecrets method:
memoryStore.getSecrets = async () => {
const response = await fetch("<remote-url>");
return response.data;
}
In this example, the remote URL must return valid JSON. But you could do something fancier to get secrets from different sources depending on the environment (dev, staging, production) or other variables:
const secrets = await Promise.all([
fetch("<remote-url>"),
fs.readFile("./env", "utf-8")
])
Another valuable use case is to fetch encrypted secrets from a local file or an URL. You can then decrypt the secrets on the fly:
const encryptedSecrets = await fetch("<remote-url>")
const decryptedSecrets = decrypt(encryptedSecrets, encryptionKey)
Using this method, the secrets are never stored in plain text and are only available when needed.
4. Consuming Secrets
You can then use the env
function to fetch your secrets:
const apiKey = env("API_KEY")
const allSecrets = env()
const { apiKey } = env()
If no argument is passed to the env
function, it will return all the secrets. Otherwise, it will return the secret corresponding to the key passed as an argument.
5. Demo Example
Let’s consider a scenario where you want to protect your environment variables used in an ExpressJS app:
import './setupGlobalEnv';
import express from 'express';
import { env } from '@onboardbase/global-env';
const app = express();
const SERVER_PORT = env('SERVER_PORT')
app.get('/api', async (req, res) => {
const API_KEY = env('API_KEY')
// do things ...
res.send({
ok: true
});
});
app.listen(SERVER_PORT, () => {
console.log(`Listening on port ${SERVER_PORT}!);
});
You could setup your setupGlobalEnv.ts
file as follows:
const IN_MEMORY_ENV = {
SERVER_PORT: "3000"
}
import {
setupStore,
memoryStore,
allEnv
} from "@onboardbase/global-env"
const IN_MEMORY_ENV = {
SECRET_KEY: "MOHBAD"
}
const store = {
...memoryStore,
setSecrets: async () => {},
getSecrets: async () => {
const REMOTE_ENV = await fetch("http://localhost:9000/env.json").then(res => res.json());
return {
...REMOTE_ENV,
...IN_MEMORY_ENV,
}
}
}
(async function () {
await setupStore(store)
})()
The sensitive environment variables are fetched from a remote source that’s only accessible from the same machine, while the non-sensitive ones are stored in-memory.
The environment variables server could also take care of encrypting the secrets and you could decrypt them locally for additional security.
Caveats
It’s important to note that global env
is intended for server-side applications. Storing sensitive information on the client-side is never recommended. Sensitive data should always be securely managed and transmitted to the client when needed on a per-request basis. For example, client API tokens should be stored encrypted in a secure database and retrieved when needed.
global env
is not a replacement for a secret management solution. It’s a simple tool to dynamically fetch environment variables. It’s not a complete solution for managing secrets in a production environment. For example, it doesn’t provide a way to encrypt or rotate secrets out of the box. If you need a more robust solution, consider using Onboardbase’s CLI or SDK.
If we take Onboardbase’s CLI as an example, the following command will run a NodeJS application with the secrets fetched from the env.json
file:
curl -s http://localhost:9000/env.json | onboardbase run "yarn start" --source-path -
But a more robust solution would be to define environment variables in Onboardbase and inject them at runtime:
onboardbase setup
onboardbase secrets:upload -p [PROJECT NAME] -e [ENVIRONMENT NAME] '{"API_KEY":"value"}'
onboardbase run --command="yarn run"
Onboardbase takes care of encrypting your secrets at rest and in transit, as well as setting up the correct environment variables at runtime.
Code Walkthrough
global env
is an open-source project so you can dive into the source code to understand how it operates under the hood. To briefly summarize how the codebase works:
global env
is a Typescript project that weights 2KB- We use jest for testing
- We use
std-env
as a lightweight proxy to set and access environment variables global env
provides a nice typed key-value store for environment variables. The code is 70-lines long.
We also encourage you to contribute to the project if you feel like it. We’re always happy to work with the community!
Conclusion
By replacing direct access to process.env
with the env
function, global env
ensures your secrets are only available when needed and never persist anywhere, reducing the risk of accidental exposure and unauthorized access, but without compromising on good developer experience. The package is lightweight, compatibly with Typescript, and easy to use.
global env
is the result of years of experience in secret management at Onboardbase. It’s part of the core of Onboardbase’s platform, so we hope you find it useful in your own projects. Don’t hesitate to reach out if you have any questions or feedback!
Make sure to subscribe to our newsletter to get notified when we release new open source software like this. See you around and take care!
Subscribe to our newsletter
The latest news, articles, features and resources of Onboardbase, sent to your inbox weekly