Stop Leaking Env Secrets: Your Password Manager Holds the Key

nickgatzoulis

Nick Gatzoulis

Posted on July 24, 2024

Stop Leaking Env Secrets: Your Password Manager Holds the Key

One of the biggest cybersecurity issues projects may face, other than getting rekt by a NullPointerException in a channel file, is having the environment variables secrets leaked in their codebase. It doesn’t matter if the repository is private or public or only have development and staging environment variables exposed. As long as we commit these values to the codebase, we are always at an elevated risk of getting compromised.

The Wake-up Call: 12.8 Million Open API keys leaked

The rapid rise in the sophistication of LLMs and even more so the wide availability of ChatGPT as an API has led to many fellow engineers and enthusiasts experimenting with their own implementations that utilise Open API. The sad news, however, is that GitHub users inadvertently exposed a staggering number of secrets in 2023. GitGuardian's State of Secrets Sprawl report revealed over 12.8 million authentication credentials and sensitive data were leaked across 3 million public repositories.

The leaked data consisted of account passwords, API keys, TLS/SSL certificates, encryption keys, cloud credentials, OAuth tokens, and more (Source: GitGuardian). A fellow GitHub contributor, Bipin Jitiya has created a search syntax for finding such keys & tokens which still yields plenty of results (Source: GitHub).

Why This Happens? Because we’re lazy…

Indeed, we as developers quite often take the easiest and quickest path when it comes to security, even in our own little projects, let alone in big multi-million corporation projects. Whether it’s the startup culture we’re part of or we just want get things done quickly and iteratively, at the end of the day most if it can be attributed to laziness.

Why bother with the complexity of a secrets vault when I can just slap my API key right into an environment variable and get instant results? Who needs fancy tools like Vault by HashiCorp or Doppler when OPENAI_API_KEY=HelloWorld does the trick? Until you decide to make your repository public and everyone now knows your secret key.

I will be honest with you, I have done the exact same thing many times over until I realised I don’t really need complex tools or fancy solutions. I can use what most of us already use; a Password Manager!

And to be fair, if you fall into or overlap with the “Lazy Programmer” category, you’re one of a kind developers who would rather take time to set up a process once and leverage it to save time in the future, therefore, by definition, you should be quite happy with the solution at hand. Being Lazy Programmer can work for you or against you and it all depends on your will-power.

Password Manager Holds the Key (literally)

If you're part of a company, odds are you're already using a password manager. These handy tools have evolved far beyond just storing passwords – they now handle encrypted notes, identities, credentials, SSH keys, and more. As long as your password manager offers a command-line interface (CLI), you can tap into this secure storage to protect your sensitive keys. I'm currently using 1Password, and this article focuses on how you can leverage 1Password Business and its CLI for this very purpose.

The Example Setup: Next.js with 1Password CLI as Secret Vault

Prerequisites

This tutorial assumes that you have completed the prerequisites below:

  1. Sign up for 1Password Business plan here: https://1password.com/pricing
  2. Install the 1Password for Desktop (Mac, Windows or Linux)
  3. Install the 1Password CLI (v2) by following the steps here: https://developer.1password.com/docs/cli/get-started/
  4. Set up a Next.js project using the following considerations:

    What is your project named? Up to you
    Would you like to use TypeScript? Yes
    Would you like to use ESLint? Yes
    Would you like to use Tailwind CSS? No
    Would you like to use src/ directory Yes
    Would you like to use App Router? (recommended) No
    Would you like to customize the default import alias (@/)? **Yes*

  5. Create an .env.local file in your the root folder of your Next.js app.
    Add a single environment variable: PRIVATE_API_KEY=helloworld123

  6. Your project structure should now look like this:

    https://imgur.com/kj6OiVB.png


Creating Secrets Vault & Adding Secret Credentials

The concept of “Vaults” should be quite self-explanatory but just to be clear; a “vault” is a container of encrypted data (i.e. key-value pair secrets). You may create as many vaults as you wish depending on your requirements, however, in this case, we will go with just a single vault called “Next.js Localhost Vault”. This means that we are going to set up a vault per environment per project.

Create a new vault called “Next.js Localhost Vault”

https://imgur.com/b0z1khC

Then click on “New Item” → “API Credentials”

https://imgur.com/zvWxRp9.png

And add your PRIVATE_API_KEY environment variable key-value pair (and call the item same as the key)

https://imgur.com/ZVBEngf.png

We have now created an API credential & secret reference that we can access both via the 1Password app and the CLI.


How to Find & Read the Secret References

1Password allows us to access and interact with the secret references that can be called as URIs in the following format: op://<vault-name>/<item-name>/[section-name/]<field-name>

In our case, as per the screenshot above, the values we’re going to use for each part of the URI are:

  • <vault-name>Next.js Localhost Vault
  • <item-name>PRIVATE_API_KEY
  • [section-name/] → This is optional, we don’t use it for API credential items.
  • <field-name>credential

Therefore, our final URI will look like: op://Next.js Localhost Vault/PRIVATE_API_KEY/credential

Don’t worry, we don’t have to type it manually every time. We can get the secret reference URI in two ways:

  1. Via the 1Password App by click the dropdown chevron in the credential field of the API credential and clicking on Copy Secret Reference

    https://imgur.com/5ru8wpT.png

  2. Via the 1Password CLI by typing op item get PRIVATE_API_KEY --vault "Next.js Localhost Vault" in the terminal.

Now that we have the secret reference URI, we can read the value by typing: op read "op://Next.js Localhost Vault/PRIVATE_API_KEY/credential" (notice that the command syntax is: op read "[secret reference]" )


Update the Next.js API Endpoint to Return the Environment Variable Value

At the beginning of this tutorial, we created an .env.local file which contained a single environment variable. For the sake of the demonstration, let’s update the Next.js API endpoint that came pre-bundled with the base installation to return the environment variable in its JSON response.

That endpoint is available in the src/pages/api/hello.ts and we need to update the following:

  • The Data interface to: type Data = { key?: string; };
  • The response to: res.status(200).json({ key: process.env.PRIVATE_API_KEY });

Start the Next.js instance by typing npm run dev and navigate to http://localhost:3000/api/hello to see the following JSON object returned:

{ 
    "key": "helloworld123" 
}
Enter fullscreen mode Exit fullscreen mode

We have to stop the instance after we see the key-value pair JSON so that we can prepare for the next step.


Replace Plain-text Environment Variables with 1Password Secret References

Our next step is to replace that plain-text environment variable with the 1Password secret reference as follows:

PRIVATE_API_KEY="op://Next.js Localhost Vault/PRIVATE_API_KEY/credential"

And then we also have to update our package.json file so that our environment variables linked to 1Password secret references can be decoded by 1Password at runtime and its values can be assumed by the Next.js instance.

In package.json we will update the dev command to be as follows;

"dev": "op run --env-file=\".env.local\" -- next dev"

This will run the 1Password’s CLI command op run which will parse the env file passed as an argument to the flag --env.file , and make it available to the Next.js instance upon launch.

Start the Next.js instance by typing npm run dev, navigate to http://localhost:3000/api/hello and observe that it now says helloworld123


Conclusion & Next Steps

Hopefully by now you have understood how crucial it is to keep your keys and tokens secure even if you are using a single environment or just developing a side project using Open API keys. It is a good practice to always keep your credentials safe to at best avoid disappointment, or at worst avoid a security a breach.

You don’t have to opt for enterprise-level complex implementations for your side projects or even for serious startups with acquired funding. A simple tool like a password manager may already have everything you need and spending 15-20 minutes setting it up once (and less than 2 minutes once you learn the ropes) will save you both from risking your security and from using bad practices such as saving API keys in your Notes.

Take one step further with Service Accounts.

I will write a separate post on how you can leverage 1Password’s Service Accounts to further increase security of this implementation but in the meanwhile, feel free to read more here: https://developer.1password.com/docs/service-accounts/use-with-1password-cli

Other alternatives

  • Dotenv now has a package called dotenv-vault which allows you to easily sync your .env files safely using a secrets vault; https://www.dotenv.org/
  • AWS Secrets Manager can also be used for storing your secrets and you can use the AWS CLI to retrieve them. The only minor downside is that you will need a script to sync the environment variables into .env files
💖 💪 🙅 🚩
nickgatzoulis
Nick Gatzoulis

Posted on July 24, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related