← Back to blog
Global Env in Node.js: Safeguarding Your Secrets

Global Env in Node.js: Safeguarding Your Secrets

authors photo
Written by Dante Lex
Friday, October 27th 2023

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.

Onboardbase dashboard

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.

Github repository

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