New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@dotenvx/dotenvx

Package Overview
Dependencies
Maintainers
0
Versions
189
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@dotenvx/dotenvx - npm Package Compare versions

Comparing version 1.19.3 to 1.20.0

src/lib/helpers/catchAndLog.js

13

CHANGELOG.md

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

## [Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.19.3...main)
## [Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.20.0...main)
### Changed
* respect `process.env.DOTENV_PRIVATE_KEY` and/or `process.env.DOTENV_PUBLIC_KEY` on `set` ([#401](https://github.com/dotenvx/dotenvx/pull/401))
* respect `process.env.DOTENV_PRIVATE_KEY` and/or `process.env.DOTENV_PUBLIC_KEY` on `encrypt` ([#411](https://github.com/dotenvx/dotenvx/pull/411))
* respect `process.env.DOTENV_PRIVATE_KEY` on `decrypt` ([#412](https://github.com/dotenvx/dotenvx/pull/412))
* change `logger.help` to use brighter blue ([#414](https://github.com/dotenvx/dotenvx/pull/414))
### Removed
* remove `main.decrypt,encrypt,set` ([#410](https://github.com/dotenvx/dotenvx/pull/410))
## 1.19.3

@@ -9,0 +20,0 @@

2

package.json
{
"version": "1.19.3",
"version": "1.20.0",
"name": "@dotenvx/dotenvx",

@@ -4,0 +4,0 @@ "description": "a better dotenv–from the creator of `dotenv`",

@@ -1793,3 +1793,3 @@ [![dotenvx](https://dotenvx.com/better-banner.png)](https://dotenvx.com)

Run `$ dotenvx vault migrate` and follow the instructions.
Run `$ dotenvx ext vault migrate` and follow the instructions.

@@ -1796,0 +1796,0 @@  

const fsx = require('./../../lib/helpers/fsx')
const { logger } = require('./../../shared/logger')
const main = require('./../../lib/main')
const Decrypt = require('./../../lib/services/decrypt')
const catchAndLog = require('../../lib/helpers/catchAndLog')
function decrypt () {

@@ -10,2 +12,4 @@ const options = this.opts()

const envs = this.envs
let errorCount = 0

@@ -16,11 +20,11 @@

const {
processedEnvFiles
} = main.decrypt(options.envFile, options.key, options.excludeKey)
processedEnvs
} = new Decrypt(envs, options.key, options.excludeKey).run()
for (const processedEnvFile of processedEnvFiles) {
if (processedEnvFile.error) {
for (const processedEnv of processedEnvs) {
if (processedEnv.error) {
errorCount += 1
console.error(processedEnvFile.error.message)
console.error(processedEnv.error.message)
} else {
console.log(processedEnvFile.envSrc)
console.log(processedEnv.envSrc)
}

@@ -37,25 +41,25 @@ }

const {
processedEnvFiles,
processedEnvs,
changedFilepaths,
unchangedFilepaths
} = main.decrypt(options.envFile, options.key, options.excludeKey)
} = new Decrypt(envs, options.key, options.excludeKey).run()
for (const processedEnvFile of processedEnvFiles) {
logger.verbose(`decrypting ${processedEnvFile.envFilepath} (${processedEnvFile.filepath})`)
for (const processedEnv of processedEnvs) {
logger.verbose(`decrypting ${processedEnv.envFilepath} (${processedEnv.filepath})`)
if (processedEnvFile.error) {
if (processedEnv.error) {
errorCount += 1
if (processedEnvFile.error.code === 'MISSING_ENV_FILE') {
logger.error(processedEnvFile.error.message)
logger.help(`? add one with [echo "HELLO=World" > ${processedEnvFile.envFilepath}] and re-run [dotenvx decrypt]`)
if (processedEnv.error.code === 'MISSING_ENV_FILE') {
logger.error(processedEnv.error.message)
logger.help(`? add one with [echo "HELLO=World" > ${processedEnv.envFilepath}] and re-run [dotenvx decrypt]`)
} else {
logger.error(processedEnvFile.error.message)
logger.error(processedEnv.error.message)
}
} else if (processedEnvFile.changed) {
fsx.writeFileX(processedEnvFile.filepath, processedEnvFile.envSrc)
} else if (processedEnv.changed) {
fsx.writeFileX(processedEnv.filepath, processedEnv.envSrc)
logger.verbose(`decrypted ${processedEnvFile.envFilepath} (${processedEnvFile.filepath})`)
logger.verbose(`decrypted ${processedEnv.envFilepath} (${processedEnv.filepath})`)
} else {
logger.verbose(`no changes ${processedEnvFile.envFilepath} (${processedEnvFile.filepath})`)
logger.verbose(`no changes ${processedEnv.envFilepath} (${processedEnv.filepath})`)
}

@@ -76,12 +80,3 @@ }

} catch (error) {
logger.error(error.message)
if (error.help) {
logger.help(error.help)
}
if (error.debug) {
logger.debug(error.debug)
}
if (error.code) {
logger.debug(`ERROR_CODE: ${error.code}`)
}
catchAndLog(error)
process.exit(1)

@@ -88,0 +83,0 @@ }

const fsx = require('./../../lib/helpers/fsx')
const { logger } = require('./../../shared/logger')
const main = require('./../../lib/main')
const Encrypt = require('./../../lib/services/encrypt')
const catchAndLog = require('../../lib/helpers/catchAndLog')
const isIgnoringDotenvKeys = require('../../lib/helpers/isIgnoringDotenvKeys')

@@ -12,10 +13,12 @@

const envs = this.envs
// stdout - should not have a try so that exit codes can surface to stdout
if (options.stdout) {
const {
processedEnvFiles
} = main.encrypt(options.envFile, options.key, options.excludeKey)
processedEnvs
} = new Encrypt(envs, options.key, options.excludeKey).run()
for (const processedEnvFile of processedEnvFiles) {
console.log(processedEnvFile.envSrc)
for (const processedEnv of processedEnvs) {
console.log(processedEnv.envSrc)
}

@@ -26,22 +29,25 @@ process.exit(0) // exit early

const {
processedEnvFiles,
processedEnvs,
changedFilepaths,
unchangedFilepaths
} = main.encrypt(options.envFile, options.key, options.excludeKey)
} = new Encrypt(envs, options.key, options.excludeKey).run()
for (const processedEnvFile of processedEnvFiles) {
logger.verbose(`encrypting ${processedEnvFile.envFilepath} (${processedEnvFile.filepath})`)
if (processedEnvFile.error) {
if (processedEnvFile.error.code === 'MISSING_ENV_FILE') {
logger.warn(processedEnvFile.error.message)
logger.help(`? add one with [echo "HELLO=World" > ${processedEnvFile.envFilepath}] and re-run [dotenvx encrypt]`)
for (const processedEnv of processedEnvs) {
logger.verbose(`encrypting ${processedEnv.envFilepath} (${processedEnv.filepath})`)
if (processedEnv.error) {
if (processedEnv.error.code === 'MISSING_ENV_FILE') {
logger.warn(processedEnv.error.message)
logger.help(`? add one with [echo "HELLO=World" > ${processedEnv.envFilepath}] and re-run [dotenvx encrypt]`)
} else {
logger.warn(processedEnvFile.error.message)
logger.warn(processedEnv.error.message)
if (processedEnv.error.help) {
logger.help(processedEnv.error.help)
}
}
} else if (processedEnvFile.changed) {
fsx.writeFileX(processedEnvFile.filepath, processedEnvFile.envSrc)
} else if (processedEnv.changed) {
fsx.writeFileX(processedEnv.filepath, processedEnv.envSrc)
logger.verbose(`encrypted ${processedEnvFile.envFilepath} (${processedEnvFile.filepath})`)
logger.verbose(`encrypted ${processedEnv.envFilepath} (${processedEnv.filepath})`)
} else {
logger.verbose(`no changes ${processedEnvFile.envFilepath} (${processedEnvFile.filepath})`)
logger.verbose(`no changes ${processedEnv.envFilepath} (${processedEnv.filepath})`)
}

@@ -58,5 +64,5 @@ }

for (const processedEnvFile of processedEnvFiles) {
if (processedEnvFile.privateKeyAdded) {
logger.success(`✔ key added to .env.keys (${processedEnvFile.privateKeyName})`)
for (const processedEnv of processedEnvs) {
if (processedEnv.privateKeyAdded) {
logger.success(`✔ key added to .env.keys (${processedEnv.privateKeyName})`)

@@ -67,16 +73,7 @@ if (!isIgnoringDotenvKeys()) {

logger.help2(`ℹ run [${processedEnvFile.privateKeyName}='${processedEnvFile.privateKey}' dotenvx run -- yourcommand] to test decryption locally`)
logger.help2(`ℹ run [${processedEnv.privateKeyName}='${processedEnv.privateKey}' dotenvx run -- yourcommand] to test decryption locally`)
}
}
} catch (error) {
logger.error(error.message)
if (error.help) {
logger.help(error.help)
}
if (error.debug) {
logger.debug(error.debug)
}
if (error.code) {
logger.debug(`ERROR_CODE: ${error.code}`)
}
catchAndLog(error)
process.exit(1)

@@ -83,0 +80,0 @@ }

const fsx = require('./../../lib/helpers/fsx')
const { logger } = require('./../../shared/logger')
const main = require('./../../lib/main')
const Sets = require('./../../lib/services/sets')
const catchAndLog = require('../../lib/helpers/catchAndLog')
const isIgnoringDotenvKeys = require('../../lib/helpers/isIgnoringDotenvKeys')

@@ -22,7 +23,9 @@

try {
const envs = this.envs
const {
processedEnvFiles,
processedEnvs,
changedFilepaths,
unchangedFilepaths
} = main.set(key, value, options.envFile, encrypt)
} = new Sets(key, value, envs, encrypt).run()

@@ -35,17 +38,20 @@ let withEncryption = ''

for (const processedEnvFile of processedEnvFiles) {
logger.verbose(`setting for ${processedEnvFile.envFilepath}`)
for (const processedEnv of processedEnvs) {
logger.verbose(`setting for ${processedEnv.envFilepath}`)
if (processedEnvFile.error) {
if (processedEnvFile.error.code === 'MISSING_ENV_FILE') {
logger.warn(processedEnvFile.error.message)
logger.help(`? add one with [echo "HELLO=World" > ${processedEnvFile.envFilepath}] and re-run [dotenvx set]`)
if (processedEnv.error) {
if (processedEnv.error.code === 'MISSING_ENV_FILE') {
logger.warn(processedEnv.error.message)
logger.help(`? add one with [echo "HELLO=World" > ${processedEnv.envFilepath}] and re-run [dotenvx set]`)
} else {
logger.warn(processedEnvFile.error.message)
logger.warn(processedEnv.error.message)
if (processedEnv.error.help) {
logger.help(processedEnv.error.help)
}
}
} else {
fsx.writeFileX(processedEnvFile.filepath, processedEnvFile.envSrc)
fsx.writeFileX(processedEnv.filepath, processedEnv.envSrc)
logger.verbose(`${processedEnvFile.key} set${withEncryption} (${processedEnvFile.envFilepath})`)
logger.debug(`${processedEnvFile.key} set${withEncryption} to ${processedEnvFile.value} (${processedEnvFile.envFilepath})`)
logger.verbose(`${processedEnv.key} set${withEncryption} (${processedEnv.envFilepath})`)
logger.debug(`${processedEnv.key} set${withEncryption} to ${processedEnv.value} (${processedEnv.envFilepath})`)
}

@@ -62,5 +68,5 @@ }

for (const processedEnvFile of processedEnvFiles) {
if (processedEnvFile.privateKeyAdded) {
logger.success(`✔ key added to .env.keys (${processedEnvFile.privateKeyName})`)
for (const processedEnv of processedEnvs) {
if (processedEnv.privateKeyAdded) {
logger.success(`✔ key added to .env.keys (${processedEnv.privateKeyName})`)

@@ -71,16 +77,7 @@ if (!isIgnoringDotenvKeys()) {

logger.help2(`ℹ run [${processedEnvFile.privateKeyName}='${processedEnvFile.privateKey}' dotenvx get ${key}] to test decryption locally`)
logger.help2(`ℹ run [${processedEnv.privateKeyName}='${processedEnv.privateKey}' dotenvx get ${key}] to test decryption locally`)
}
}
} catch (error) {
logger.error(error.message)
if (error.help) {
logger.help(error.help)
}
if (error.debug) {
logger.debug(error.debug)
}
if (error.code) {
logger.debug(`ERROR_CODE: ${error.code}`)
}
catchAndLog(error)
process.exit(1)

@@ -87,0 +84,0 @@ }

@@ -62,3 +62,2 @@ #!/usr/bin/env node

this.envs = envs
runAction.apply(this, args)

@@ -82,3 +81,2 @@ })

this.envs = envs
getAction.apply(this, args)

@@ -95,6 +93,9 @@ })

.argument('value', 'value')
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)', '.env')
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
.option('-c, --encrypt', 'encrypt value (default: true)', true)
.option('-p, --plain', 'store value as plain text', false)
.action(setAction)
.action(function (...args) {
this.envs = envs
setAction.apply(this, args)
})

@@ -105,7 +106,10 @@ // dotenvx encrypt

.description('convert .env file(s) to encrypted .env file(s)')
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)')
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
.option('-k, --key <keys...>', 'keys(s) to encrypt (default: all keys in file)')
.option('-ek, --exclude-key <excludeKeys...>', 'keys(s) to exclude from encryption (default: none)')
.option('--stdout', 'send to stdout')
.action(encryptAction)
.action(function (...args) {
this.envs = envs
encryptAction.apply(this, args)
})

@@ -116,7 +120,10 @@ // dotenvx decrypt

.description('convert encrypted .env file(s) to plain .env file(s)')
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)')
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
.option('-k, --key <keys...>', 'keys(s) to decrypt (default: all keys in file)')
.option('-ek, --exclude-key <excludeKeys...>', 'keys(s) to exclude from decryption (default: none)')
.option('--stdout', 'send to stdout')
.action(decryptAction)
.action(function (...args) {
this.envs = envs
decryptAction.apply(this, args)
})

@@ -123,0 +130,0 @@ // dotenvx keypair

const { decrypt } = require('eciesjs')
const truncate = require('./truncate')
const PREFIX = 'encrypted:'

@@ -30,5 +32,5 @@

if (e.message === 'Invalid private key') {
decryptionError = new Error('private key looks invalid')
decryptionError = new Error(`private key [${truncate(privateKey)}] looks invalid`)
} else if (e.message === 'Unsupported state or unable to authenticate data') {
decryptionError = new Error('private key looks wrong')
decryptionError = new Error(`private key [${truncate(privateKey)}] looks wrong`)
} else if (e.message === 'Point of length 65 was invalid. Expected 33 compressed bytes or 65 uncompressed bytes') {

@@ -35,0 +37,0 @@ decryptionError = new Error('encrypted data looks malformed')

const ENCRYPTION_PATTERN = /^encrypted:.+/
function isEncrypted (key, value) {
function isEncrypted (value) {
return ENCRYPTION_PATTERN.test(value)

@@ -5,0 +5,0 @@ }

@@ -10,3 +10,3 @@ const dotenv = require('dotenv')

for (const [key, value] of Object.entries(parsed)) {
const result = isEncrypted(key, value) || isPublicKey(key, value)
const result = isEncrypted(value) || isPublicKey(key, value)
if (!result) {

@@ -13,0 +13,0 @@ return false

@@ -13,6 +13,3 @@ // @ts-check

const Run = require('./services/run')
const Sets = require('./services/sets')
const Keypair = require('./services/keypair')
const Encrypt = require('./services/encrypt')
const Decrypt = require('./services/decrypt')
const Genexample = require('./services/genexample')

@@ -200,17 +197,2 @@

/** @type {import('./main').set} */
const set = function (key, value, envFile, encrypt) {
return new Sets(key, value, envFile, encrypt).run()
}
/** @type {import('./main').encrypt} */
const encrypt = function (envFile, key, excludeKey) {
return new Encrypt(envFile, key, excludeKey).run()
}
/** @type {import('./main').encrypt} */
const decrypt = function (envFile, key, excludeKey) {
return new Decrypt(envFile, key, excludeKey).run()
}
/** @type {import('./main').keypair} */

@@ -227,7 +209,4 @@ const keypair = function (envFile, key) {

// actions related
encrypt,
decrypt,
ls,
get,
set,
keypair,

@@ -234,0 +213,0 @@ genexample,

@@ -6,19 +6,19 @@ const fsx = require('./../helpers/fsx')

const smartDotenvPrivateKey = require('./../helpers/smartDotenvPrivateKey')
const TYPE_ENV_FILE = 'envFile'
const guessPrivateKeyName = require('./../helpers/guessPrivateKeyName')
const findPrivateKey = require('./../helpers/findPrivateKey')
const decryptValue = require('./../helpers/decryptValue')
const isEncrypted = require('./../helpers/isEncrypted')
const replace = require('./../helpers/replace')
const detectEncoding = require('./../helpers/detectEncoding')
const determineEnvs = require('./../helpers/determineEnvs')
class Decrypt {
/**
* @param {string|string[]} [envFile]
* @param {string|string[]} [key]
* @param {string|string[]} [excludeKey]
**/
constructor (envFile = '.env', key = [], excludeKey = []) {
this.envFile = envFile
constructor (envs = [], key = [], excludeKey = []) {
this.envs = determineEnvs(envs, process.env)
this.key = key
this.excludeKey = excludeKey
this.processedEnvFiles = []
this.processedEnvs = []
this.changedFilepaths = new Set()

@@ -29,87 +29,88 @@ this.unchangedFilepaths = new Set()

run () {
const envFilepaths = this._envFilepaths()
const keys = this._keys()
// example
// envs [
// { type: 'envFile', value: '.env' }
// ]
this.keys = this._keys()
const excludeKeys = this._excludeKeys()
const exclude = picomatch(excludeKeys)
const include = picomatch(keys, { ignore: excludeKeys })
for (const envFilepath of envFilepaths) {
const filepath = path.resolve(envFilepath)
this.exclude = picomatch(excludeKeys)
this.include = picomatch(this.keys, { ignore: excludeKeys })
const row = {}
row.keys = []
row.filepath = filepath
row.envFilepath = envFilepath
for (const env of this.envs) {
if (env.type === TYPE_ENV_FILE) {
this._decryptEnvFile(env.value)
}
}
try {
// get the src
let src = fsx.readFileX(filepath)
return {
processedEnvs: this.processedEnvs,
changedFilepaths: [...this.changedFilepaths],
unchangedFilepaths: [...this.unchangedFilepaths]
}
}
// if DOTENV_PRIVATE_KEY_* already set in process.env then use it
const privateKey = smartDotenvPrivateKey(envFilepath)
row.privateKey = privateKey
row.privateKeyName = guessPrivateKeyName(filepath)
_decryptEnvFile (envFilepath) {
const row = {}
row.keys = []
row.type = TYPE_ENV_FILE
// track possible changes
row.changed = false
const filepath = path.resolve(envFilepath)
row.filepath = filepath
row.envFilepath = envFilepath
// iterate over all non-encrypted values and encrypt them
const parsed = dotenv.parse(src)
for (const [key, value] of Object.entries(parsed)) {
// key excluded - don't decrypt it
if (exclude(key)) {
continue
}
try {
const encoding = this._detectEncoding(filepath)
let envSrc = fsx.readFileX(filepath, { encoding })
const envParsed = dotenv.parse(envSrc)
// key effectively excluded (by not being in the list of includes) - don't encrypt it
if (keys.length > 0 && !include(key)) {
continue
}
const privateKey = findPrivateKey(envFilepath)
const privateKeyName = guessPrivateKeyName(envFilepath)
const encrypted = isEncrypted(key, value)
if (encrypted) {
row.keys.push(key) // track key(s)
row.privateKey = privateKey
row.privateKeyName = privateKeyName
row.changed = false // track possible changes
const decryptedValue = decryptValue(value, privateKey)
// once newSrc is built write it out
src = replace(src, key, decryptedValue)
row.changed = true // track change
}
for (const [key, value] of Object.entries(envParsed)) {
// key excluded - don't decrypt it
if (this.exclude(key)) {
continue
}
if (row.changed) {
row.envSrc = src
this.changedFilepaths.add(envFilepath)
} else {
row.envSrc = src
this.unchangedFilepaths.add(envFilepath)
// key effectively excluded (by not being in the list of includes) - don't decrypt it
if (this.keys.length > 0 && !this.include(key)) {
continue
}
} catch (e) {
if (e.code === 'ENOENT') {
const error = new Error(`missing ${envFilepath} file (${filepath})`)
error.code = 'MISSING_ENV_FILE'
row.error = error
} else {
row.error = e
const encrypted = isEncrypted(value)
if (encrypted) {
row.keys.push(key) // track key(s)
const decryptedValue = decryptValue(value, privateKey)
// once newSrc is built write it out
envSrc = replace(envSrc, key, decryptedValue)
row.changed = true // track change
}
}
this.processedEnvFiles.push(row)
}
row.envSrc = envSrc
if (row.changed) {
this.changedFilepaths.add(envFilepath)
} else {
this.unchangedFilepaths.add(envFilepath)
}
} catch (e) {
if (e.code === 'ENOENT') {
const error = new Error(`missing ${envFilepath} file (${filepath})`)
error.code = 'MISSING_ENV_FILE'
return {
processedEnvFiles: this.processedEnvFiles,
changedFilepaths: [...this.changedFilepaths],
unchangedFilepaths: [...this.unchangedFilepaths]
row.error = error
} else {
row.error = e
}
}
}
_envFilepaths () {
if (!Array.isArray(this.envFile)) {
return [this.envFile]
}
return this.envFile
this.processedEnvs.push(row)
}

@@ -132,4 +133,8 @@

}
_detectEncoding (filepath) {
return detectEncoding(filepath)
}
}
module.exports = Decrypt

@@ -6,20 +6,24 @@ const fsx = require('./../helpers/fsx')

const findOrCreatePublicKey = require('./../helpers/findOrCreatePublicKey')
const TYPE_ENV_FILE = 'envFile'
const guessPrivateKeyName = require('./../helpers/guessPrivateKeyName')
const guessPublicKeyName = require('./../helpers/guessPublicKeyName')
const encryptValue = require('./../helpers/encryptValue')
const isEncrypted = require('./../helpers/isEncrypted')
const replace = require('./../helpers/replace')
const detectEncoding = require('./../helpers/detectEncoding')
const determineEnvs = require('./../helpers/determineEnvs')
const findPrivateKey = require('./../helpers/findPrivateKey')
const findPublicKey = require('./../helpers/findPublicKey')
const keyPair = require('./../helpers/keyPair')
const truncate = require('./../helpers/truncate')
const isPublicKey = require('./../helpers/isPublicKey')
const replace = require('./../helpers/replace')
class Encrypt {
/**
* @param {string|string[]} [envFile]
* @param {string|string[]} [key]
* @param {string|string[]} [excludeKey]
**/
constructor (envFile = '.env', key = [], excludeKey = []) {
this.envFile = envFile
constructor (envs = [], key = [], excludeKey = []) {
this.envs = determineEnvs(envs, process.env)
this.key = key
this.excludeKey = excludeKey
this.processedEnvFiles = []
this.processedEnvs = []
this.changedFilepaths = new Set()

@@ -30,100 +34,163 @@ this.unchangedFilepaths = new Set()

run () {
const envFilepaths = this._envFilepaths()
const keys = this._keys()
// example
// envs [
// { type: 'envFile', value: '.env' }
// ]
this.keys = this._keys()
const excludeKeys = this._excludeKeys()
const exclude = picomatch(excludeKeys)
const include = picomatch(keys, { ignore: excludeKeys })
for (const envFilepath of envFilepaths) {
const filepath = path.resolve(envFilepath)
this.exclude = picomatch(excludeKeys)
this.include = picomatch(this.keys, { ignore: excludeKeys })
const row = {}
row.keys = []
row.filepath = filepath
row.envFilepath = envFilepath
for (const env of this.envs) {
if (env.type === TYPE_ENV_FILE) {
this._encryptEnvFile(env.value)
}
}
try {
// get the original src
let src = fsx.readFileX(filepath)
// get/generate the public key
return {
processedEnvs: this.processedEnvs,
changedFilepaths: [...this.changedFilepaths],
unchangedFilepaths: [...this.unchangedFilepaths]
}
}
_encryptEnvFile (envFilepath) {
const row = {}
row.keys = []
row.type = TYPE_ENV_FILE
const filename = path.basename(envFilepath)
const filepath = path.resolve(envFilepath)
row.filepath = filepath
row.envFilepath = envFilepath
try {
const encoding = this._detectEncoding(filepath)
let envSrc = fsx.readFileX(filepath, { encoding })
const envParsed = dotenv.parse(envSrc)
let publicKey
let privateKey
const publicKeyName = guessPublicKeyName(envFilepath)
const privateKeyName = guessPrivateKeyName(envFilepath)
const existingPrivateKey = findPrivateKey(envFilepath)
const existingPublicKey = findPublicKey(envFilepath)
if (existingPrivateKey) {
const kp = keyPair(existingPrivateKey)
publicKey = kp.publicKey
privateKey = kp.privateKey
// if derivation doesn't match what's in the file (or preset in env)
if (existingPublicKey && existingPublicKey !== publicKey) {
const error = new Error(`derived public key (${truncate(publicKey)}) does not match the existing public key (${truncate(existingPublicKey)})`)
error.code = 'INVALID_DOTENV_PRIVATE_KEY'
error.help = `debug info: ${privateKeyName}=${truncate(existingPrivateKey)} (derived ${publicKeyName}=${truncate(publicKey)} vs existing ${publicKeyName}=${truncate(existingPublicKey)})`
throw error
}
} else if (existingPublicKey) {
publicKey = existingPublicKey
} else {
// .env.keys
let keysSrc = ''
const envKeysFilepath = path.join(path.dirname(filepath), '.env.keys')
const {
envSrc,
keysSrc,
publicKey,
privateKey,
publicKeyAdded,
privateKeyAdded
} = findOrCreatePublicKey(filepath, envKeysFilepath)
if (fsx.existsSync(envKeysFilepath)) {
keysSrc = fsx.readFileX(envKeysFilepath)
}
// handle .env.keys write
fsx.writeFileX(envKeysFilepath, keysSrc)
// preserve shebang
const [firstLine, ...remainingLines] = envSrc.split('\n')
let firstLinePreserved = ''
if (firstLine.startsWith('#!')) {
firstLinePreserved = firstLine + '\n'
envSrc = remainingLines.join('\n')
}
src = envSrc // src was potentially modified by findOrCreatePublicKey so we set it again here
const kp = keyPair() // generates a fresh keypair in memory
publicKey = kp.publicKey
privateKey = kp.privateKey
row.changed = publicKeyAdded // track change
row.publicKey = publicKey
row.privateKey = privateKey
row.privateKeyAdded = privateKeyAdded
row.privateKeyName = guessPrivateKeyName(filepath)
// publicKey
const prependPublicKey = [
'#/-------------------[DOTENV_PUBLIC_KEY]--------------------/',
'#/ public-key encryption for .env files /',
'#/ [how it works](https://dotenvx.com/encryption) /',
'#/----------------------------------------------------------/',
`${publicKeyName}="${publicKey}"`,
'',
`# ${filename}`
].join('\n')
// iterate over all non-encrypted values and encrypt them
const parsed = dotenv.parse(src)
for (const [key, value] of Object.entries(parsed)) {
// key excluded - don't encrypt it
if (exclude(key)) {
continue
}
// privateKey
const firstTimeKeysSrc = [
'#/------------------!DOTENV_PRIVATE_KEYS!-------------------/',
'#/ private decryption keys. DO NOT commit to source control /',
'#/ [how it works](https://dotenvx.com/encryption) /',
'#/----------------------------------------------------------/'
].join('\n')
const appendPrivateKey = [
`# ${filename}`,
`${privateKeyName}="${privateKey}"`,
''
].join('\n')
// key effectively excluded (by not being in the list of includes) - don't encrypt it
if (keys.length > 0 && !include(key)) {
continue
}
envSrc = `${firstLinePreserved}${prependPublicKey}\n${envSrc}`
keysSrc = keysSrc.length > 1 ? keysSrc : `${firstTimeKeysSrc}\n`
keysSrc = `${keysSrc}\n${appendPrivateKey}`
const encrypted = isEncrypted(key, value) || isPublicKey(key, value)
if (!encrypted) {
row.keys.push(key) // track key(s)
// write to .env.keys
fsx.writeFileX(envKeysFilepath, keysSrc)
const encryptedValue = encryptValue(value, publicKey)
// once newSrc is built write it out
src = replace(src, key, encryptedValue)
row.privateKeyAdded = true
}
row.changed = true // track change
}
row.publicKey = publicKey
row.privateKey = privateKey
row.privateKeyName = privateKeyName
// iterate over all non-encrypted values and encrypt them
for (const [key, value] of Object.entries(envParsed)) {
// key excluded - don't encrypt it
if (this.exclude(key)) {
continue
}
if (row.changed) {
row.envSrc = src
this.changedFilepaths.add(envFilepath)
} else {
row.envSrc = src
this.unchangedFilepaths.add(envFilepath)
// key effectively excluded (by not being in the list of includes) - don't encrypt it
if (this.keys.length > 0 && !this.include(key)) {
continue
}
} catch (e) {
if (e.code === 'ENOENT') {
const error = new Error(`missing ${envFilepath} file (${filepath})`)
error.code = 'MISSING_ENV_FILE'
row.error = error
} else {
row.error = e
const encrypted = isEncrypted(value) || isPublicKey(key, value)
if (!encrypted) {
row.keys.push(key) // track key(s)
const encryptedValue = encryptValue(value, publicKey)
// once newSrc is built write it out
envSrc = replace(envSrc, key, encryptedValue)
row.changed = true // track change
}
}
this.processedEnvFiles.push(row)
}
row.envSrc = envSrc
if (row.changed) {
this.changedFilepaths.add(envFilepath)
} else {
this.unchangedFilepaths.add(envFilepath)
}
} catch (e) {
if (e.code === 'ENOENT') {
const error = new Error(`missing ${envFilepath} file (${filepath})`)
error.code = 'MISSING_ENV_FILE'
return {
processedEnvFiles: this.processedEnvFiles,
changedFilepaths: [...this.changedFilepaths],
unchangedFilepaths: [...this.unchangedFilepaths]
row.error = error
} else {
row.error = e
}
}
}
_envFilepaths () {
if (!Array.isArray(this.envFile)) {
return [this.envFile]
}
return this.envFile
this.processedEnvs.push(row)
}

@@ -146,4 +213,8 @@

}
_detectEncoding (filepath) {
return detectEncoding(filepath)
}
}
module.exports = Encrypt
const fsx = require('./../helpers/fsx')
const path = require('path')
const dotenv = require('dotenv')
const childProcess = require('child_process')

@@ -9,4 +8,2 @@ const TYPE_ENV = 'env'

const TYPE_ENV_VAULT_FILE = 'envVaultFile'
const DEFAULT_ENVS = [{ type: TYPE_ENV_FILE, value: '.env' }]
const DEFAULT_ENV_VAULTS = [{ type: TYPE_ENV_VAULT_FILE, value: '.env.vault' }]

@@ -17,12 +14,9 @@ const inject = require('./../helpers/inject')

const parseEnvironmentFromDotenvKey = require('./../helpers/parseEnvironmentFromDotenvKey')
const guessPrivateKeyFilename = require('./../helpers/guessPrivateKeyFilename')
const guessPrivateKeyName = require('./../helpers/guessPrivateKeyName')
const detectEncoding = require('./../helpers/detectEncoding')
const findPrivateKey = require('./../helpers/findPrivateKey')
const determineEnvs = require('./../helpers/determineEnvs')
const Keypair = require('./../services/keypair')
class Run {
constructor (envs = [], overload = false, DOTENV_KEY = '', processEnv = process.env) {
this.dotenvPrivateKeyNames = Object.keys(processEnv).filter(key => key.startsWith('DOTENV_PRIVATE_KEY')) // important, must be first. used by determineEnvs
this.envs = this._determineEnvs(envs, DOTENV_KEY)
this.envs = determineEnvs(envs, processEnv, DOTENV_KEY)
this.overload = overload

@@ -48,3 +42,3 @@ this.DOTENV_KEY = DOTENV_KEY

for (const env of this.envs) {
if (env.type === TYPE_ENV_VAULT_FILE) {
if (env.type === TYPE_ENV_VAULT_FILE) { // deprecate someday - for deprecated .env.vault files
this._injectEnvVaultFile(env.value)

@@ -102,3 +96,3 @@ } else if (env.type === TYPE_ENV_FILE) {

const privateKey = this._determinePrivateKey(envFilepath)
const privateKey = findPrivateKey(envFilepath)
const { parsed, processEnv, warnings } = parseDecryptEvalExpand(src, privateKey, this.processEnv)

@@ -196,57 +190,2 @@ row.parsed = parsed

_determineEnvsFromDotenvPrivateKey () {
const envs = []
for (const privateKeyName of this.dotenvPrivateKeyNames) {
const filename = guessPrivateKeyFilename(privateKeyName)
envs.push({ type: TYPE_ENV_FILE, value: filename })
}
return envs
}
_determineEnvs (envs = [], DOTENV_KEY = '') {
if (!envs || envs.length <= 0) {
// if process.env.DOTENV_PRIVATE_KEY or process.env.DOTENV_PRIVATE_KEY_${environment} is set, assume inline encryption methodology
if (this.dotenvPrivateKeyNames.length > 0) {
return this._determineEnvsFromDotenvPrivateKey()
}
if (DOTENV_KEY.length > 0) {
// if DOTENV_KEY is set then default to look for .env.vault file
return DEFAULT_ENV_VAULTS
} else {
return DEFAULT_ENVS // default to .env file expectation
}
} else {
let fileAlreadySpecified = false // can be .env or .env.vault type
for (const env of envs) {
// if DOTENV_KEY set then we are checking if a .env.vault file is already specified
if (DOTENV_KEY.length > 0 && env.type === TYPE_ENV_VAULT_FILE) {
fileAlreadySpecified = true
}
// if DOTENV_KEY not set then we are checking if a .env file is already specified
if (DOTENV_KEY.length <= 0 && env.type === TYPE_ENV_FILE) {
fileAlreadySpecified = true
}
}
// return early since envs array objects already contain 1 .env.vault or .env file
if (fileAlreadySpecified) {
return envs
}
// no .env.vault or .env file specified as a flag so we assume either .env.vault (if dotenv key is set) or a .env file
if (DOTENV_KEY.length > 0) {
// if DOTENV_KEY is set then default to look for .env.vault file
return [...DEFAULT_ENV_VAULTS, ...envs]
} else {
// if no DOTENV_KEY then default to look for .env file
return [...DEFAULT_ENVS, ...envs]
}
}
}
// handle scenario for comma separated keys - for use with key rotation

@@ -279,27 +218,4 @@ // example: DOTENV_KEY="dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=prod,dotenv://:key_7890@dotenvx.com/vault/.env.vault?environment=prod"

}
_determinePrivateKey (envFilepath) {
const privateKeyName = guessPrivateKeyName(envFilepath)
let privateKey
try {
// if installed as sibling module
const projectRoot = path.resolve(process.cwd())
const dotenvxProPath = require.resolve('@dotenvx/dotenvx-pro', { paths: [projectRoot] })
const { keypair } = require(dotenvxProPath)
privateKey = keypair(envFilepath, privateKeyName)
} catch (_e) {
try {
// if installed as binary cli
privateKey = childProcess.execSync(`dotenvx-pro keypair ${privateKeyName} -f ${envFilepath}`, { stdio: ['pipe', 'pipe', 'ignore'] }).toString().trim()
} catch (_e) {
// fallback to local KeyPair - smart enough to handle process.env, .env.keys, etc
privateKey = new Keypair(envFilepath, privateKeyName).run()
}
}
return privateKey
}
}
module.exports = Run
const fsx = require('./../helpers/fsx')
const path = require('path')
const dotenv = require('dotenv')
const findOrCreatePublicKey = require('./../helpers/findOrCreatePublicKey')
const TYPE_ENV_FILE = 'envFile'
const guessPrivateKeyName = require('./../helpers/guessPrivateKeyName')
const guessPublicKeyName = require('./../helpers/guessPublicKeyName')
const encryptValue = require('./../helpers/encryptValue')
const decryptValue = require('./../helpers/decryptValue')
const replace = require('./../helpers/replace')
const detectEncoding = require('./../helpers/detectEncoding')
const determineEnvs = require('./../helpers/determineEnvs')
const findPrivateKey = require('./../helpers/findPrivateKey')
const findPublicKey = require('./../helpers/findPublicKey')
const keyPair = require('./../helpers/keyPair')
const truncate = require('./../helpers/truncate')
const isEncrypted = require('./../helpers/isEncrypted')
class Sets {
constructor (key, value, envFile = '.env', encrypt = true) {
constructor (key, value, envs = [], encrypt = true) {
this.envs = determineEnvs(envs, process.env)
this.key = key
this.value = value
this.envFile = envFile
this.encrypt = encrypt
this.processedEnvFiles = []
this.processedEnvs = []
this.changedFilepaths = new Set()
this.unchangedFilepaths = new Set()
this.readableFilepaths = new Set()
}
run () {
const envFilepaths = this._envFilepaths()
for (const envFilepath of envFilepaths) {
const filepath = path.resolve(envFilepath)
// example
// envs [
// { type: 'envFile', value: '.env' }
// ]
const row = {}
row.key = this.key
row.value = this.value
row.filepath = filepath
row.envFilepath = envFilepath
row.changed = false
for (const env of this.envs) {
if (env.type === TYPE_ENV_FILE) {
this._setEnvFile(env.value)
}
}
try {
let value = this.value
let src = fsx.readFileX(filepath)
row.originalValue = dotenv.parse(src)[row.key] || null
return {
processedEnvs: this.processedEnvs,
changedFilepaths: [...this.changedFilepaths],
unchangedFilepaths: [...this.unchangedFilepaths]
}
}
if (this.encrypt) {
const envKeysFilepath = path.join(path.dirname(filepath), '.env.keys')
const {
envSrc,
keysSrc,
publicKey,
privateKey,
publicKeyAdded,
privateKeyAdded
} = findOrCreatePublicKey(filepath, envKeysFilepath)
_setEnvFile (envFilepath) {
const row = {}
row.key = this.key || null
row.value = this.value || null
row.type = TYPE_ENV_FILE
// handle .env.keys write
fsx.writeFileX(envKeysFilepath, keysSrc)
const filename = path.basename(envFilepath)
const filepath = path.resolve(envFilepath)
row.filepath = filepath
row.envFilepath = envFilepath
row.changed = false
src = envSrc // src was potentially modified by findOrCreatePublicKey so we set it again here
try {
const encoding = this._detectEncoding(filepath)
let envSrc = fsx.readFileX(filepath, { encoding })
const envParsed = dotenv.parse(envSrc)
row.originalValue = envParsed[row.key] || null
const wasPlainText = !isEncrypted(row.originalValue)
this.readableFilepaths.add(envFilepath)
value = encryptValue(value, publicKey)
if (this.encrypt) {
let publicKey
let privateKey
row.changed = publicKeyAdded // track change
row.encryptedValue = value
row.publicKey = publicKey
row.privateKey = privateKey
row.privateKeyAdded = privateKeyAdded
row.privateKeyName = guessPrivateKeyName(filepath)
}
const publicKeyName = guessPublicKeyName(envFilepath)
const privateKeyName = guessPrivateKeyName(envFilepath)
const existingPrivateKey = findPrivateKey(envFilepath)
const existingPublicKey = findPublicKey(envFilepath)
if (value !== row.originalValue) {
row.envSrc = replace(src, this.key, value)
if (existingPrivateKey) {
const kp = keyPair(existingPrivateKey)
publicKey = kp.publicKey
privateKey = kp.privateKey
this.changedFilepaths.add(envFilepath)
row.changed = true
if (row.originalValue) {
row.originalValue = decryptValue(row.originalValue, privateKey)
}
// if derivation doesn't match what's in the file (or preset in env)
if (existingPublicKey && existingPublicKey !== publicKey) {
const error = new Error(`derived public key (${truncate(publicKey)}) does not match the existing public key (${truncate(existingPublicKey)})`)
error.code = 'INVALID_DOTENV_PRIVATE_KEY'
error.help = `debug info: ${privateKeyName}=${truncate(existingPrivateKey)} (derived ${publicKeyName}=${truncate(publicKey)} vs existing ${publicKeyName}=${truncate(existingPublicKey)})`
throw error
}
} else if (existingPublicKey) {
publicKey = existingPublicKey
} else {
row.envSrc = src
// .env.keys
let keysSrc = ''
const envKeysFilepath = path.join(path.dirname(filepath), '.env.keys')
if (fsx.existsSync(envKeysFilepath)) {
keysSrc = fsx.readFileX(envKeysFilepath)
}
this.unchangedFilepaths.add(envFilepath)
// preserve shebang
const [firstLine, ...remainingLines] = envSrc.split('\n')
let firstLinePreserved = ''
if (firstLine.startsWith('#!')) {
firstLinePreserved = firstLine + '\n'
envSrc = remainingLines.join('\n')
}
const kp = keyPair() // generates a fresh keypair in memory
publicKey = kp.publicKey
privateKey = kp.privateKey
// publicKey
const prependPublicKey = [
'#/-------------------[DOTENV_PUBLIC_KEY]--------------------/',
'#/ public-key encryption for .env files /',
'#/ [how it works](https://dotenvx.com/encryption) /',
'#/----------------------------------------------------------/',
`${publicKeyName}="${publicKey}"`,
'',
`# ${filename}`
].join('\n')
// privateKey
const firstTimeKeysSrc = [
'#/------------------!DOTENV_PRIVATE_KEYS!-------------------/',
'#/ private decryption keys. DO NOT commit to source control /',
'#/ [how it works](https://dotenvx.com/encryption) /',
'#/----------------------------------------------------------/'
].join('\n')
const appendPrivateKey = [
`# ${filename}`,
`${privateKeyName}="${privateKey}"`,
''
].join('\n')
envSrc = `${firstLinePreserved}${prependPublicKey}\n${envSrc}`
keysSrc = keysSrc.length > 1 ? keysSrc : `${firstTimeKeysSrc}\n`
keysSrc = `${keysSrc}\n${appendPrivateKey}`
// write to .env.keys
fsx.writeFileX(envKeysFilepath, keysSrc)
row.privateKeyAdded = true
}
} catch (e) {
if (e.code === 'ENOENT') {
const error = new Error(`missing ${envFilepath} file (${filepath})`)
error.code = 'MISSING_ENV_FILE'
row.error = error
} else {
row.error = e
}
row.publicKey = publicKey
row.privateKey = privateKey
row.encryptedValue = encryptValue(this.value, publicKey)
row.privateKeyName = privateKeyName
}
this.processedEnvFiles.push(row)
const goingFromPlainTextToEncrypted = wasPlainText && this.encrypt
const valueChanged = this.value !== row.originalValue
if (goingFromPlainTextToEncrypted || valueChanged) {
row.envSrc = replace(envSrc, this.key, row.encryptedValue || this.value)
this.changedFilepaths.add(envFilepath)
row.changed = true
} else {
row.envSrc = envSrc
this.unchangedFilepaths.add(envFilepath)
row.changed = false
}
} catch (e) {
if (e.code === 'ENOENT') {
const error = new Error(`missing ${envFilepath} file (${filepath})`)
error.code = 'MISSING_ENV_FILE'
row.error = error
} else {
row.error = e
}
}
return {
processedEnvFiles: this.processedEnvFiles,
changedFilepaths: [...this.changedFilepaths],
unchangedFilepaths: [...this.unchangedFilepaths]
}
this.processedEnvs.push(row)
}
_envFilepaths () {
if (!Array.isArray(this.envFile)) {
return [this.envFile]
}
return this.envFile
_detectEncoding (filepath) {
return detectEncoding(filepath)
}

@@ -104,0 +187,0 @@ }

@@ -10,3 +10,5 @@ const depth = require('../lib/helpers/colorDepth')

['plum', 35], // mapped to magenta
['red', 31]
['red', 31],
['electricblue', 36],
['dodgerblue', 36]
])

@@ -21,3 +23,5 @@

['plum', 182],
['red', 196]
['red', 196],
['electricblue', 45],
['dodgerblue', 33]
])

@@ -24,0 +28,0 @@

@@ -31,3 +31,3 @@ const packageJson = require('../lib/helpers/packageJson')

const successv = getColor('olive') // yellow-ish tint that 'looks' like dotenv
const help = getColor('blue')
const help = getColor('dodgerblue')
const help2 = getColor('gray')

@@ -34,0 +34,0 @@ const verbose = getColor('plum')

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