@dotenvx/dotenvx
Advanced tools
Comparing version 0.4.0 to 0.5.0
{ | ||
"version": "0.4.0", | ||
"version": "0.5.0", | ||
"name": "@dotenvx/dotenvx", | ||
@@ -4,0 +4,0 @@ "description": "a better dotenv–from the creator of `dotenv`", |
@@ -161,3 +161,3 @@ ![dotenvx](https://dotenvx.com/better-banner.png) | ||
``` | ||
dotenvx encrypt | ||
$ dotenvx encrypt | ||
``` | ||
@@ -164,0 +164,0 @@ |
@@ -5,3 +5,2 @@ #!/usr/bin/env node | ||
const { Command } = require('commander') | ||
const dotenv = require('dotenv') | ||
const program = new Command() | ||
@@ -63,36 +62,93 @@ | ||
// convert to array if needed | ||
let optionEnvFile = options.envFile | ||
if (!Array.isArray(optionEnvFile)) { | ||
optionEnvFile = [optionEnvFile] | ||
} | ||
// load from .env.vault file | ||
if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) { | ||
const filepath = helpers.resolvePath('.env.vault') | ||
const env = {} | ||
const readableFilepaths = new Set() | ||
const written = new Set() | ||
if (!fs.existsSync(filepath)) { | ||
logger.error(`you set DOTENV_KEY but your .env.vault file is missing: ${filepath}`) | ||
} else { | ||
logger.verbose(`injecting encrypted env from ${filepath}`) | ||
for (const envFilepath of optionEnvFile) { | ||
const filepath = helpers.resolvePath(envFilepath) | ||
try { | ||
logger.debug(`reading encrypted env from ${filepath}`) | ||
const src = fs.readFileSync(filepath, { encoding: ENCODING }) | ||
logger.verbose(`injecting env from ${filepath}`) | ||
logger.debug(`parsing encrypted env from ${filepath}`) | ||
const parsedVault = main.parse(src) | ||
try { | ||
logger.debug(`reading env from ${filepath}`) | ||
const src = fs.readFileSync(filepath, { encoding: ENCODING }) | ||
logger.debug(`decrypting encrypted env from ${filepath}`) | ||
// 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 dotenvKeys = process.env.DOTENV_KEY.split(',') | ||
const length = dotenvKeys.length | ||
logger.debug(`parsing env from ${filepath}`) | ||
const parsed = main.parse(src) | ||
let decrypted | ||
for (let i = 0; i < length; i++) { | ||
try { | ||
// Get full dotenvKey | ||
const dotenvKey = dotenvKeys[i].trim() | ||
logger.debug(`writing env from ${filepath}`) | ||
const result = main.write(process.env, parsed, options.overload) | ||
const key = helpers._parseEncryptionKeyFromDotenvKey(dotenvKey) | ||
const ciphertext = helpers._parseCipherTextFromDotenvKeyAndParsedVault(dotenvKey, parsedVault) | ||
readableFilepaths.add(envFilepath) | ||
result.written.forEach(key => written.add(key)) | ||
} catch (e) { | ||
logger.warn(e) | ||
// Decrypt | ||
decrypted = main.decrypt(ciphertext, key) | ||
break | ||
} catch (error) { | ||
// last key | ||
if (i + 1 >= length) { | ||
throw error | ||
} | ||
// try next key | ||
} | ||
} | ||
logger.debug(decrypted) | ||
logger.debug(`parsing decrypted env from ${filepath}`) | ||
const parsed = main.parse(decrypted) | ||
logger.debug(`writing decrypted env from ${filepath}`) | ||
const result = main.write(process.env, parsed, options.overload) | ||
logger.info(`injecting ${result.written.size} environment ${helpers.pluralize('variable', result.written.size)} from encrypted .env.vault`) | ||
} catch (e) { | ||
logger.error(e) | ||
} | ||
} | ||
} | ||
} else { | ||
// convert to array if needed | ||
let optionEnvFile = options.envFile | ||
if (!Array.isArray(optionEnvFile)) { | ||
optionEnvFile = [optionEnvFile] | ||
} | ||
if (readableFilepaths.size > 0) { | ||
logger.info(`injecting ${written.size} environment ${helpers.pluralize('variable', written.size)} from ${[...readableFilepaths]}`) | ||
const readableFilepaths = new Set() | ||
const written = new Set() | ||
for (const envFilepath of optionEnvFile) { | ||
const filepath = helpers.resolvePath(envFilepath) | ||
logger.verbose(`injecting env from ${filepath}`) | ||
try { | ||
logger.debug(`reading env from ${filepath}`) | ||
const src = fs.readFileSync(filepath, { encoding: ENCODING }) | ||
logger.debug(`parsing env from ${filepath}`) | ||
const parsed = main.parse(src) | ||
logger.debug(`writing env from ${filepath}`) | ||
const result = main.write(process.env, parsed, options.overload) | ||
readableFilepaths.add(envFilepath) | ||
result.written.forEach(key => written.add(key)) | ||
} catch (e) { | ||
logger.warn(e) | ||
} | ||
} | ||
if (readableFilepaths.size > 0) { | ||
logger.info(`injecting ${written.size} environment ${helpers.pluralize('variable', written.size)} from ${[...readableFilepaths]}`) | ||
} | ||
} | ||
@@ -108,3 +164,3 @@ | ||
helpers.executeCommand(subCommand, env) | ||
helpers.executeCommand(subCommand, process.env) | ||
} | ||
@@ -130,3 +186,3 @@ }) | ||
const dotenvKeys = (dotenv.configDotenv({ path: '.env.keys' }).parsed || {}) | ||
const dotenvKeys = (main.configDotenv({ path: '.env.keys' }).parsed || {}) | ||
@@ -176,4 +232,4 @@ for (const envFilepath of optionEnvFile) { | ||
const dotenvKeys = (dotenv.configDotenv({ path: '.env.keys' }).parsed || {}) | ||
const dotenvVaults = (dotenv.configDotenv({ path: '.env.vault' }).parsed || {}) | ||
const dotenvKeys = (main.configDotenv({ path: '.env.keys' }).parsed || {}) | ||
const dotenvVaults = (main.configDotenv({ path: '.env.vault' }).parsed || {}) | ||
@@ -219,6 +275,4 @@ for (const envFilepath of optionEnvFile) { | ||
logger.info(`encrypted ${optionEnvFile} to .env.vault`) | ||
// logger.info(`encrypting`) | ||
}) | ||
program.parse(process.argv) |
@@ -7,2 +7,3 @@ const fs = require('fs') | ||
const XXHASH_SEED = 0xABCD | ||
const NONCE_BYTES = 12 | ||
@@ -73,6 +74,6 @@ const main = require('./../lib/main') | ||
const encryptFile = function (filepath, dotenvKey, encoding) { | ||
const key = this._parseEncryptionKeyFromDotenvKey(dotenvKey) | ||
const key = _parseEncryptionKeyFromDotenvKey(dotenvKey) | ||
const message = fs.readFileSync(filepath, encoding) | ||
const ciphertext = this.encrypt(key, message) | ||
const ciphertext = encrypt(key, message) | ||
@@ -84,3 +85,3 @@ return ciphertext | ||
// set up nonce | ||
const nonce = this._generateNonce() | ||
const nonce = crypto.randomBytes(NONCE_BYTES) | ||
@@ -104,7 +105,7 @@ // set up cipher | ||
const changed = function (ciphertext, dotenvKey, filepath, encoding) { | ||
const key = this._parseEncryptionKeyFromDotenvKey(dotenvKey) | ||
const key = _parseEncryptionKeyFromDotenvKey(dotenvKey) | ||
const decrypted = main.decrypt(ciphertext, key) | ||
const raw = fs.readFileSync(filepath, encoding) | ||
return this.hash(decrypted) !== this.hash(raw) | ||
return hash(decrypted) !== hash(raw) | ||
} | ||
@@ -118,3 +119,8 @@ | ||
// Parse DOTENV_KEY. Format is a URI | ||
const uri = new URL(dotenvKey) | ||
let uri | ||
try { | ||
uri = new URL(dotenvKey) | ||
} catch (e) { | ||
throw new Error(`INVALID_DOTENV_KEY: ${e.message}`) | ||
} | ||
@@ -130,8 +136,25 @@ // Get decrypt key | ||
const _generateNonce = function () { | ||
return crypto.randomBytes(this._nonceBytes()) | ||
} | ||
const _parseCipherTextFromDotenvKeyAndParsedVault = function (dotenvKey, parsedVault) { | ||
// Parse DOTENV_KEY. Format is a URI | ||
let uri | ||
try { | ||
uri = new URL(dotenvKey) | ||
} catch (e) { | ||
throw new Error(`INVALID_DOTENV_KEY: ${e.message}`) | ||
} | ||
const _nonceBytes = function () { | ||
return 12 | ||
// 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 = parsedVault[environmentKey] // DOTENV_VAULT_PRODUCTION | ||
if (!ciphertext) { | ||
throw new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: cannot locate environment ${environmentKey} in your .env.vault file`) | ||
} | ||
return ciphertext | ||
} | ||
@@ -151,4 +174,3 @@ | ||
_parseEncryptionKeyFromDotenvKey, | ||
_generateNonce, | ||
_nonceBytes | ||
_parseCipherTextFromDotenvKeyAndParsedVault | ||
} |
@@ -12,2 +12,6 @@ const logger = require('./../shared/logger') | ||
const configDotenv = function (options) { | ||
return dotenv.configDotenv(options) | ||
} | ||
const parse = function (src) { | ||
@@ -61,2 +65,3 @@ const result = dotenv.parse(src) | ||
config, | ||
configDotenv, | ||
decrypt, | ||
@@ -63,0 +68,0 @@ parse, |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
22729
434
11