Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More โ†’
Socket
Sign inDemoInstall
Socket

dotenv

Package Overview
Dependencies
Maintainers
3
Versions
86
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

dotenv - npm Package Compare versions

Comparing version 16.0.3 to 16.1.0-rc1

10

CHANGELOG.md

@@ -5,4 +5,12 @@ # Changelog

## [Unreleased](https://github.com/motdotla/dotenv/compare/v16.0.3...master)
## [Unreleased](https://github.com/motdotla/dotenv/compare/v16.1.0...master)
## [16.1.0](https://github.com/motdotla/dotenv/compare/v16.0.3...v16.1.0) (2023-04-01)
### Added
- Add `.env.vault` support. ๐ŸŽ‰ ([#730](https://github.com/motdotla/dotenv/pull/730))
โ„น๏ธ `.env.vault` extends the `.env` file format standard with a localized encrypted vault file. Package it securely with your production code deploys. It's cloud agnostic so that you can deploy your secrets anywhere โ€“ย without [risky third-party integrations](https://techcrunch.com/2023/01/05/circleci-breach/).
## [16.0.3](https://github.com/motdotla/dotenv/compare/v16.0.2...v16.0.3) (2022-09-29)

@@ -9,0 +17,0 @@

const fs = require('fs')
const path = require('path')
const os = require('os')
const crypto = require('crypto')
const packageJson = require('../package.json')

@@ -49,6 +50,108 @@

function _parseVault (options) {
const vaultPath = _vaultPath(options)
// Parse .env.vault
const result = DotenvModule._configDotenv({ path: vaultPath })
if (!result.parsed) {
throw new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`)
}
// handle scenario for comma separated keys - for use with key rotation
// example: DOTENV_KEY="dotenv://:key_1234@dotenv.org/vault/.env.vault?environment=prod,dotenv://:key_7890@dotenv.org/vault/.env.vault?environment=prod"
const keys = _dotenvKey().split(',')
const length = keys.length
let decrypted
for (let i = 0; i < length; i++) {
try {
// Get full key
const key = keys[i].trim()
// Get instructions for decrypt
const attrs = _instructions(result, key)
// Decrypt
decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key)
break
} catch (error) {
// last key
if (i + 1 >= length) {
throw error
}
// try next key
}
}
// Parse decrypted .env string
return DotenvModule.parse(decrypted)
}
function _log (message) {
console.log(`[dotenv@${version}][INFO] ${message}`)
}
function _warn (message) {
console.log(`[dotenv@${version}][WARN] ${message}`)
}
function _debug (message) {
console.log(`[dotenv@${version}][DEBUG] ${message}`)
}
function _dotenvKey () {
if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) {
return process.env.DOTENV_KEY
}
return ''
}
function _instructions (result, dotenvKey) {
// Parse DOTENV_KEY. Format is a URI
let uri
try {
uri = new URL(dotenvKey)
} catch (error) {
if (error.code === 'ERR_INVALID_URL') {
throw new Error('INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenv.org/vault/.env.vault?environment=development')
}
throw error
}
// Get decrypt key
const key = uri.password
if (!key) {
throw new Error('INVALID_DOTENV_KEY: Missing key part')
}
// Get environment
const environment = uri.searchParams.get('environment')
if (!environment) {
throw new Error('INVALID_DOTENV_KEY: Missing environment part')
}
// Get ciphertext payload
const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`
const ciphertext = result.parsed[environmentKey] // DOTENV_VAULT_PRODUCTION
if (!ciphertext) {
throw new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`)
}
return { ciphertext, key }
}
function _vaultPath (options) {
let dotenvPath = path.resolve(process.cwd(), '.env')
if (options && options.path && options.path.length > 0) {
dotenvPath = options.path
}
// Locate .env.vault
return dotenvPath.endsWith('.vault') ? dotenvPath : `${dotenvPath}.vault`
}
function _resolveHome (envPath) {

@@ -58,4 +161,33 @@ return envPath[0] === '~' ? path.join(os.homedir(), envPath.slice(1)) : envPath

// Populates process.env from .env file
function config (options) {
function _configVault (options) {
_log('Loading env from encrypted .env.vault')
const parsed = DotenvModule._parseVault(options)
const debug = Boolean(options && options.debug)
const override = Boolean(options && options.override)
// Set process.env
for (const key of Object.keys(parsed)) {
if (Object.prototype.hasOwnProperty.call(process.env, key)) {
if (override === true) {
process.env[key] = parsed[key]
}
if (debug) {
if (override === true) {
_debug(`"${key}" is already defined in \`process.env\` and WAS overwritten`)
} else {
_debug(`"${key}" is already defined in \`process.env\` and was NOT overwritten`)
}
}
} else {
process.env[key] = parsed[key]
}
}
return { parsed }
}
function _configDotenv (options) {
let dotenvPath = path.resolve(process.cwd(), '.env')

@@ -77,3 +209,3 @@ let encoding = 'utf8'

// Specifying an encoding returns a string instead of a buffer
const parsed = DotenvModule.parse(fs.readFileSync(dotenvPath, { encoding }))
const parsed = parse(fs.readFileSync(dotenvPath, { encoding }))

@@ -90,5 +222,5 @@ Object.keys(parsed).forEach(function (key) {

if (override === true) {
_log(`"${key}" is already defined in \`process.env\` and WAS overwritten`)
_debug(`"${key}" is already defined in \`process.env\` and WAS overwritten`)
} else {
_log(`"${key}" is already defined in \`process.env\` and was NOT overwritten`)
_debug(`"${key}" is already defined in \`process.env\` and was NOT overwritten`)
}

@@ -102,3 +234,3 @@ }

if (debug) {
_log(`Failed to load ${dotenvPath} ${e.message}`)
_debug(`Failed to load ${dotenvPath} ${e.message}`)
}

@@ -110,9 +242,68 @@

// Populates process.env from .env file
function config (options) {
const vaultPath = _vaultPath(options)
// fallback to original dotenv if DOTENV_KEY is not set
if (_dotenvKey().length === 0) {
return DotenvModule._configDotenv(options)
}
// dotenvKey exists but .env.vault file does not exist
if (!fs.existsSync(vaultPath)) {
_warn(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`)
return DotenvModule._configDotenv(options)
}
return DotenvModule._configVault(options)
}
function decrypt (encrypted, keyStr) {
const key = Buffer.from(keyStr.slice(-64), 'hex')
let ciphertext = Buffer.from(encrypted, 'base64')
const nonce = ciphertext.slice(0, 12)
const authTag = ciphertext.slice(-16)
ciphertext = ciphertext.slice(12, -16)
try {
const aesgcm = crypto.createDecipheriv('aes-256-gcm', key, nonce)
aesgcm.setAuthTag(authTag)
return `${aesgcm.update(ciphertext)}${aesgcm.final()}`
} catch (error) {
const isRange = error instanceof RangeError
const invalidKeyLength = error.message === 'Invalid key length'
const decryptionFailed = error.message === 'Unsupported state or unable to authenticate data'
if (isRange || invalidKeyLength) {
const msg = 'INVALID_DOTENV_KEY: It must be 64 characters long (or more)'
throw new Error(msg)
} else if (decryptionFailed) {
const msg = 'DECRYPTION_FAILED: Please check your DOTENV_KEY'
throw new Error(msg)
} else {
console.error('Error: ', error.code)
console.error('Error: ', error.message)
throw error
}
}
}
const DotenvModule = {
_configDotenv,
_configVault,
_parseVault,
config,
decrypt,
parse
}
module.exports._configDotenv = DotenvModule._configDotenv
module.exports._configVault = DotenvModule._configVault
module.exports._parseVault = DotenvModule._parseVault
module.exports.config = DotenvModule.config
module.exports.decrypt = DotenvModule.decrypt
module.exports.parse = DotenvModule.parse
module.exports = DotenvModule

2

package.json
{
"name": "dotenv",
"version": "16.0.3",
"version": "16.1.0-rc1",
"description": "Loads environment variables from .env file",

@@ -5,0 +5,0 @@ "main": "lib/main.js",

@@ -30,8 +30,14 @@ <div align="center">

</a>
<br>
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=dotenv&utm_source=github">
<div>
<img src="https://res.cloudinary.com/dotenv-org/image/upload/c_scale,w_400/v1665605496/68747470733a2f2f73696e647265736f726875732e636f6d2f6173736574732f7468616e6b732f776f726b6f732d6c6f676f2d77686974652d62672e737667_zdmsbu.svg" width="270" alt="WorkOS">
</div>
<b>Your App, Enterprise Ready.</b>
<div>
<sup>Add Single Sign-On, Multi-Factor Auth, and more, in minutes instead of months.</sup>
</div>
</a>
<hr>
<br>
<br>
<br>
<br>
</div>

@@ -47,4 +53,2 @@

[![BuildStatus](https://img.shields.io/travis/motdotla/dotenv/master.svg?style=flat-square)](https://travis-ci.org/motdotla/dotenv)
[![Build status](https://ci.appveyor.com/api/projects/status/github/motdotla/dotenv?svg=true)](https://ci.appveyor.com/project/motdotla/dotenv/branch/master)
[![NPM version](https://img.shields.io/npm/v/dotenv.svg?style=flat-square)](https://www.npmjs.com/package/dotenv)

@@ -54,9 +58,13 @@ [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard)

[![LICENSE](https://img.shields.io/github/license/motdotla/dotenv.svg)](LICENSE)
[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org)
[![Featured on Openbase](https://badges.openbase.com/js/featured/dotenv.svg?token=eE0hWPkhC2JGSD4G9hwg5C54EBxjJAyvurGfQsYoKiQ=)](https://openbase.com/js/dotenv?utm_source=embedded&utm_medium=badge&utm_campaign=featured-badge&utm_term=js/dotenv)
[![Limited Edition Tee Original](https://img.shields.io/badge/Limited%20Edition%20Tee%20%F0%9F%91%95-Original-yellow?labelColor=black&style=plastic)](https://dotenv.gumroad.com/l/original)
[![Limited Edition Tee Redacted](https://img.shields.io/badge/Limited%20Edition%20Tee%20%F0%9F%91%95-Redacted-gray?labelColor=black&style=plastic)](https://dotenv.gumroad.com/l/redacted)
## Install
* [๐ŸŒฑ Install](#-install)
* [๐Ÿ—๏ธ Usage (.env)](#%EF%B8%8F-usage)
* [๐Ÿš€ Deploying (.env.vault) ๐Ÿ†•](#-deploying)
* [๐ŸŒด Examples](#-examples)
* [๐Ÿฆฎ Docs](#-documentation)
* [โ“ FAQ](#-faq)
* [โฑ๏ธ Changelog](./CHANGELOG.md)
## ๐ŸŒฑ Install
```bash

@@ -69,3 +77,3 @@ # install locally (recommended)

## Usage
## ๐Ÿ—๏ธ Usage

@@ -177,4 +185,65 @@ Create a `.env` file in the root of your project:

## Examples
## ๐Ÿš€ Deploying
**Note: Unreleased. Coming April 17, 2023! Releasing as dotenv@16.1.0.**
Up until recently (year 2023), we did not have an opinion on deploying your secrets to production. Dotenv had been focused on solving development secrets only. However, with the increasing number of secrets breaches like the [CircleCI breach](https://techcrunch.com/2023/01/05/circleci-breach/) we have formed an opinion.
Don't scatter your secrets across multiple platforms and tools. Use a `.env.vault` file.
The `.env.vault` file encrypts your secrets and decrypts them just-in-time on boot of your application. It uses a `DOTENV_KEY` environment variable that you set on your cloud platform or server. If there is a secrets breach, an attacker only gains access to your decryption key, not your secrets. They would additionally have to gain access to your codebase, find your .env.vault file, and decrypt it to get your secrets. This is much harder and more time consuming for an attacker.
It works in 3 easy steps.
### 1. Create .env.ENVIRONMENT files
In addition to your `.env` (development) file, create a `.env.ci`, `.env.staging`, and `.env.production` file.
(Have a custom environment? Just append it's name. For example, `.env.prod`.)
Put your respective secrets in each of those files, just like you always have with your `.env` files. These files should NOT be committed to code.
### 2. Generate .env.vault file
Run the build command to generate your `.env.vault` file.
```
$ npx dotenv-vault local build
```
This command will read the contents of each of your `.env.*` files, encrypt them, and inject the encrypted versions into your `.env.vault` file. For example:
```
# .env.vault (generated with npx dotenv-vault local build)
DOTENV_VAULT_DEVELOPMENT="X/GOMD7h/Fygjyq3+K2zbdyTBUBVA+mLivaSebqDMnLAencDGu9YvJji"
DOTENV_VAULT_CI="SNnKvHTezcd0B8L+81lhcig+6GfkRxnlrgS1GG/2tJZ7KghOEJnM"
DOTENV_VAULT_PRODUCTION="FudgivxdMrCKOKUeN+QieuCAoGiC2MstXL8JU6Pp4ILYu9wEwfqe4ne3e2jcVys="
DOTENV_VAULT_STAGING="CZXrvrTusPLJlgm62uEppwCKZt6zEr4TGwlP8Z0McJd7I8KBF522JnhT9/8="
```
Commit your `.env.vault` file safely to code. It SHOULD be committed to code.
### 3. Set DOTENV_KEY
The build command also created a `.env.keys` file for you. This is where your `DOTENV_KEY` decryption keys live per environment.
```
# DOTENV_KEYs (generated with npx dotenv-vault local build)
DOTENV_KEY_DEVELOPMENT="dotenv://:key_fc5c0d276e032a1e5ff295f59d7b63db75b0ae1a5a82ad411f4887c23dc78bd1@dotenv.local/vault/.env.vault?environment=development"
DOTENV_KEY_CI="dotenv://:key_c6bc0b1269b53ee852b269c4ea6d82d82619081f2faddb1e05894fbe90c1ef46@dotenv.local/vault/.env.vault?environment=ci"
DOTENV_KEY_STAGING="dotenv://:key_09ec9bfe7a4512b71b3b1ab12aa2f843f47b8c9dc7d0d954e206f37ca125da69@dotenv.local/vault/.env.vault?environment=staging"
```
Go to your web server or cloud platform and set the environment variable `DOTENV_KEY` with the production value. For example, in heroku I'd run the following command.
```
heroku config:set DOTENV_KEY=dotenv://:key_bfa00115ecacb678ba44376526b2f0b3131aa0060f18de357a63eda08af6a7fe@dotenv.local/vault/.env.vault?environment=production
```
Then deploy your code. On boot, the `dotenv` library (>= 16.1.0) will see that a `DOTENV_KEY` is set and use its value to decrypt the production contents of the `.env.vault` file and inject them into your process.
No more scattered secrets across multiple platforms and tools.
## ๐ŸŒด Examples
See [examples](https://github.com/dotenv-org/examples) of using dotenv with various frameworks, languages, and configurations.

@@ -196,4 +265,5 @@

* [nestjs](https://github.com/dotenv-org/examples/tree/master/dotenv-nestjs)
* [fastify](https://github.com/dotenv-org/examples/tree/master/dotenv-fastify)
## Documentation
## ๐Ÿฆฎ Documentation

@@ -294,3 +364,3 @@ Dotenv exposes two functions:

## FAQ
## โ“ FAQ

@@ -454,1 +524,5 @@ ### Why is the `.env` file not loading my environment variables successfully?

Projects that expand it often use the [keyword "dotenv" on npm](https://www.npmjs.com/search?q=keywords:dotenv).
[![Limited Edition Tee Original](https://img.shields.io/badge/Limited%20Edition%20Tee%20%F0%9F%91%95-Original-yellow?labelColor=black&style=plastic)](https://dotenv.gumroad.com/l/original)
[![Limited Edition Tee Redacted](https://img.shields.io/badge/Limited%20Edition%20Tee%20%F0%9F%91%95-Redacted-gray?labelColor=black&style=plastic)](https://dotenv.gumroad.com/l/redacted)
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with โšก๏ธ by Socket Inc