12factor-env
Advanced tools
+7
-26
@@ -42,26 +42,2 @@ import type { CleanedEnv, CleanOptions, Spec, ValidatorSpec } from 'envalid'; | ||
| declare const cleanEnv: <S extends Record<string, ValidatorSpec<unknown>>>(environment: Record<string, string | undefined>, specs: S, options?: CleanOptions<S>) => CleanedEnv<S>; | ||
| /** | ||
| * Get the secrets path lazily to allow ENV_SECRETS_PATH changes at runtime | ||
| */ | ||
| declare const getSecretsPath: () => string; | ||
| /** | ||
| * Resolve the full path to a secret file | ||
| */ | ||
| declare const secretPath: (name: string) => string; | ||
| /** | ||
| * Read a secret from a file | ||
| * @param secret - The secret file name or path | ||
| * @returns The secret value or undefined if not found | ||
| */ | ||
| declare const getSecret: (secret: string | undefined) => string | undefined; | ||
| /** | ||
| * Read secrets from files based on the secret props configuration | ||
| * Supports both direct secret files and _FILE suffix pattern | ||
| */ | ||
| declare const secretEnv: <T extends Record<string, ValidatorSpec<unknown>>>(inputEnv: Record<string, string | undefined>, secretProps: T) => Record<string, string>; | ||
| /** | ||
| * Create a validator for a secret file | ||
| * @param envFile - The environment variable name for the secret file | ||
| */ | ||
| declare const secret: (envFile?: string) => ValidatorSpec<string>; | ||
| type ValidatorFactory<T> = (spec?: Spec<T>) => ValidatorSpec<T>; | ||
@@ -83,2 +59,7 @@ /** Class 1 — always resolves to `defaultValue` when the var is unset. */ | ||
| /** | ||
| * Parse a comma-separated env value into a trimmed, non-empty string list; | ||
| * unset/blank => undefined. | ||
| */ | ||
| declare const parseEnvList: (val?: string) => string[] | undefined; | ||
| /** | ||
| * Lenient boolean validator. Unlike envalid's built-in `bool` (which rejects | ||
@@ -92,3 +73,3 @@ * e.g. `TRUE`/`yes`), this accepts `true`/`1`/`yes` case-insensitively. Safe to | ||
| /** | ||
| * Validate environment variables with secret file support | ||
| * Validate environment variables | ||
| * | ||
@@ -116,3 +97,3 @@ * @param inputEnv - The environment object (usually process.env) | ||
| declare const env: <S extends Specs, V extends Specs>(inputEnv: Record<string, string | undefined>, secrets?: S, vars?: V) => CleanedEnv<S & V>; | ||
| export { bool, boolish, cleanEnv, devDefault, email, env, EnvError, EnvMissingError, getSecret, getSecretsPath, host, json, makeValidator, num, parseEnvBoolean, parseEnvNumber, port, required, secret, secretEnv, secretPath, str, testOnly, url, withDefault }; | ||
| export { bool, boolish, cleanEnv, devDefault, email, env, EnvError, EnvMissingError, host, json, makeValidator, num, parseEnvBoolean, parseEnvList, parseEnvNumber, port, required, str, testOnly, url, withDefault }; | ||
| export type { CleanedEnv, Spec, ValidatorSpec }; |
+16
-64
| import { bool, cleanEnv as envalidCleanEnv, email, EnvError, EnvMissingError, host, json, makeValidator, num, port, str, testOnly, url } from 'envalid'; | ||
| import { readFileSync } from 'fs'; | ||
| import { join, resolve } from 'path'; | ||
| export const getNodeEnv = (environment = process.env) => { | ||
@@ -48,55 +46,2 @@ const raw = environment.NODE_ENV?.toLowerCase(); | ||
| }; | ||
| /** | ||
| * Get the secrets path lazily to allow ENV_SECRETS_PATH changes at runtime | ||
| */ | ||
| const getSecretsPath = () => process.env.ENV_SECRETS_PATH ?? '/run/secrets/'; | ||
| /** | ||
| * Resolve the full path to a secret file | ||
| */ | ||
| const secretPath = (name) => name.startsWith('/') ? name : resolve(join(getSecretsPath(), name)); | ||
| /** | ||
| * Read a secret from a file | ||
| * @param secret - The secret file name or path | ||
| * @returns The secret value or undefined if not found | ||
| */ | ||
| const getSecret = (secret) => { | ||
| if (!secret) | ||
| return undefined; | ||
| try { | ||
| const value = readFileSync(secretPath(secret), 'utf-8'); | ||
| return value.trim(); | ||
| } | ||
| catch { | ||
| return undefined; | ||
| } | ||
| }; | ||
| /** | ||
| * Read secrets from files based on the secret props configuration | ||
| * Supports both direct secret files and _FILE suffix pattern | ||
| */ | ||
| const secretEnv = (inputEnv, secretProps) => { | ||
| return Object.keys(secretProps).reduce((m, k) => { | ||
| // Try to read secret directly from file | ||
| const secret = getSecret(k); | ||
| if (secret) { | ||
| m[k] = secret; | ||
| } | ||
| else { | ||
| // Try _FILE suffix pattern (e.g., DATABASE_PASSWORD_FILE) | ||
| const secretFileKey = `${k}_FILE`; | ||
| if (Object.prototype.hasOwnProperty.call(inputEnv, secretFileKey)) { | ||
| const secretFileValue = getSecret(inputEnv[secretFileKey]); | ||
| if (secretFileValue) { | ||
| m[k] = secretFileValue; | ||
| } | ||
| } | ||
| } | ||
| return m; | ||
| }, {}); | ||
| }; | ||
| /** | ||
| * Create a validator for a secret file | ||
| * @param envFile - The environment variable name for the secret file | ||
| */ | ||
| const secret = (envFile) => str({ default: getSecret(envFile) }); | ||
| /** Class 1 — always resolves to `defaultValue` when the var is unset. */ | ||
@@ -127,2 +72,14 @@ const withDefault = (validator, defaultValue, spec = {}) => validator({ ...spec, default: defaultValue }); | ||
| /** | ||
| * Parse a comma-separated env value into a trimmed, non-empty string list; | ||
| * unset/blank => undefined. | ||
| */ | ||
| const parseEnvList = (val) => { | ||
| if (!val) | ||
| return undefined; | ||
| return val | ||
| .split(',') | ||
| .map((s) => s.trim()) | ||
| .filter(Boolean); | ||
| }; | ||
| /** | ||
| * Lenient boolean validator. Unlike envalid's built-in `bool` (which rejects | ||
@@ -140,3 +97,3 @@ * e.g. `TRUE`/`yes`), this accepts `true`/`1`/`yes` case-insensitively. Safe to | ||
| /** | ||
| * Validate environment variables with secret file support | ||
| * Validate environment variables | ||
| * | ||
@@ -166,8 +123,3 @@ * @param inputEnv - The environment object (usually process.env) | ||
| const varEnv = cleanEnv(inputEnv, vars); | ||
| // Read secrets from files | ||
| const _secrets = secretEnv(inputEnv, secrets); | ||
| // Second pass: validate secrets with file values merged in | ||
| // Include inputEnv first so env vars (e.g., Kubernetes secretKeyRef) are available, | ||
| // then varEnv overrides, then file-based secrets have highest priority | ||
| const mergedEnv = { ...inputEnv, ...varEnv, ..._secrets }; | ||
| const mergedEnv = { ...inputEnv, ...varEnv }; | ||
| return cleanEnv(mergedEnv, { ...secrets, ...vars }); | ||
@@ -177,6 +129,6 @@ }; | ||
| // Re-export from envalid | ||
| cleanEnv, devDefault, email, env, EnvError, EnvMissingError, getSecret, getSecretsPath, host, json, makeValidator, num, | ||
| cleanEnv, devDefault, email, env, EnvError, EnvMissingError, host, json, makeValidator, num, | ||
| // Lenient coercion | ||
| parseEnvBoolean, parseEnvNumber, port, required, secret, secretEnv, secretPath, str, testOnly, url, | ||
| parseEnvBoolean, parseEnvList, parseEnvNumber, port, required, str, testOnly, url, | ||
| // Fallback-class wrappers | ||
| withDefault }; |
+7
-26
@@ -42,26 +42,2 @@ import type { CleanedEnv, CleanOptions, Spec, ValidatorSpec } from 'envalid'; | ||
| declare const cleanEnv: <S extends Record<string, ValidatorSpec<unknown>>>(environment: Record<string, string | undefined>, specs: S, options?: CleanOptions<S>) => CleanedEnv<S>; | ||
| /** | ||
| * Get the secrets path lazily to allow ENV_SECRETS_PATH changes at runtime | ||
| */ | ||
| declare const getSecretsPath: () => string; | ||
| /** | ||
| * Resolve the full path to a secret file | ||
| */ | ||
| declare const secretPath: (name: string) => string; | ||
| /** | ||
| * Read a secret from a file | ||
| * @param secret - The secret file name or path | ||
| * @returns The secret value or undefined if not found | ||
| */ | ||
| declare const getSecret: (secret: string | undefined) => string | undefined; | ||
| /** | ||
| * Read secrets from files based on the secret props configuration | ||
| * Supports both direct secret files and _FILE suffix pattern | ||
| */ | ||
| declare const secretEnv: <T extends Record<string, ValidatorSpec<unknown>>>(inputEnv: Record<string, string | undefined>, secretProps: T) => Record<string, string>; | ||
| /** | ||
| * Create a validator for a secret file | ||
| * @param envFile - The environment variable name for the secret file | ||
| */ | ||
| declare const secret: (envFile?: string) => ValidatorSpec<string>; | ||
| type ValidatorFactory<T> = (spec?: Spec<T>) => ValidatorSpec<T>; | ||
@@ -83,2 +59,7 @@ /** Class 1 — always resolves to `defaultValue` when the var is unset. */ | ||
| /** | ||
| * Parse a comma-separated env value into a trimmed, non-empty string list; | ||
| * unset/blank => undefined. | ||
| */ | ||
| declare const parseEnvList: (val?: string) => string[] | undefined; | ||
| /** | ||
| * Lenient boolean validator. Unlike envalid's built-in `bool` (which rejects | ||
@@ -92,3 +73,3 @@ * e.g. `TRUE`/`yes`), this accepts `true`/`1`/`yes` case-insensitively. Safe to | ||
| /** | ||
| * Validate environment variables with secret file support | ||
| * Validate environment variables | ||
| * | ||
@@ -116,3 +97,3 @@ * @param inputEnv - The environment object (usually process.env) | ||
| declare const env: <S extends Specs, V extends Specs>(inputEnv: Record<string, string | undefined>, secrets?: S, vars?: V) => CleanedEnv<S & V>; | ||
| export { bool, boolish, cleanEnv, devDefault, email, env, EnvError, EnvMissingError, getSecret, getSecretsPath, host, json, makeValidator, num, parseEnvBoolean, parseEnvNumber, port, required, secret, secretEnv, secretPath, str, testOnly, url, withDefault }; | ||
| export { bool, boolish, cleanEnv, devDefault, email, env, EnvError, EnvMissingError, host, json, makeValidator, num, parseEnvBoolean, parseEnvList, parseEnvNumber, port, required, str, testOnly, url, withDefault }; | ||
| export type { CleanedEnv, Spec, ValidatorSpec }; |
+16
-68
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.withDefault = exports.url = exports.testOnly = exports.str = exports.secretPath = exports.secretEnv = exports.secret = exports.required = exports.port = exports.parseEnvNumber = exports.parseEnvBoolean = exports.num = exports.makeValidator = exports.json = exports.host = exports.getSecretsPath = exports.getSecret = exports.EnvMissingError = exports.EnvError = exports.env = exports.email = exports.devDefault = exports.cleanEnv = exports.boolish = exports.bool = exports.getStrictEnvMode = exports.isDevelopment = exports.isTest = exports.isProduction = exports.getNodeEnv = void 0; | ||
| exports.withDefault = exports.url = exports.testOnly = exports.str = exports.required = exports.port = exports.parseEnvNumber = exports.parseEnvList = exports.parseEnvBoolean = exports.num = exports.makeValidator = exports.json = exports.host = exports.EnvMissingError = exports.EnvError = exports.env = exports.email = exports.devDefault = exports.cleanEnv = exports.boolish = exports.bool = exports.getStrictEnvMode = exports.isDevelopment = exports.isTest = exports.isProduction = exports.getNodeEnv = void 0; | ||
| const envalid_1 = require("envalid"); | ||
@@ -17,4 +17,2 @@ Object.defineProperty(exports, "bool", { enumerable: true, get: function () { return envalid_1.bool; } }); | ||
| Object.defineProperty(exports, "url", { enumerable: true, get: function () { return envalid_1.url; } }); | ||
| const fs_1 = require("fs"); | ||
| const path_1 = require("path"); | ||
| const getNodeEnv = (environment = process.env) => { | ||
@@ -70,60 +68,2 @@ const raw = environment.NODE_ENV?.toLowerCase(); | ||
| exports.cleanEnv = cleanEnv; | ||
| /** | ||
| * Get the secrets path lazily to allow ENV_SECRETS_PATH changes at runtime | ||
| */ | ||
| const getSecretsPath = () => process.env.ENV_SECRETS_PATH ?? '/run/secrets/'; | ||
| exports.getSecretsPath = getSecretsPath; | ||
| /** | ||
| * Resolve the full path to a secret file | ||
| */ | ||
| const secretPath = (name) => name.startsWith('/') ? name : (0, path_1.resolve)((0, path_1.join)(getSecretsPath(), name)); | ||
| exports.secretPath = secretPath; | ||
| /** | ||
| * Read a secret from a file | ||
| * @param secret - The secret file name or path | ||
| * @returns The secret value or undefined if not found | ||
| */ | ||
| const getSecret = (secret) => { | ||
| if (!secret) | ||
| return undefined; | ||
| try { | ||
| const value = (0, fs_1.readFileSync)(secretPath(secret), 'utf-8'); | ||
| return value.trim(); | ||
| } | ||
| catch { | ||
| return undefined; | ||
| } | ||
| }; | ||
| exports.getSecret = getSecret; | ||
| /** | ||
| * Read secrets from files based on the secret props configuration | ||
| * Supports both direct secret files and _FILE suffix pattern | ||
| */ | ||
| const secretEnv = (inputEnv, secretProps) => { | ||
| return Object.keys(secretProps).reduce((m, k) => { | ||
| // Try to read secret directly from file | ||
| const secret = getSecret(k); | ||
| if (secret) { | ||
| m[k] = secret; | ||
| } | ||
| else { | ||
| // Try _FILE suffix pattern (e.g., DATABASE_PASSWORD_FILE) | ||
| const secretFileKey = `${k}_FILE`; | ||
| if (Object.prototype.hasOwnProperty.call(inputEnv, secretFileKey)) { | ||
| const secretFileValue = getSecret(inputEnv[secretFileKey]); | ||
| if (secretFileValue) { | ||
| m[k] = secretFileValue; | ||
| } | ||
| } | ||
| } | ||
| return m; | ||
| }, {}); | ||
| }; | ||
| exports.secretEnv = secretEnv; | ||
| /** | ||
| * Create a validator for a secret file | ||
| * @param envFile - The environment variable name for the secret file | ||
| */ | ||
| const secret = (envFile) => (0, envalid_1.str)({ default: getSecret(envFile) }); | ||
| exports.secret = secret; | ||
| /** Class 1 — always resolves to `defaultValue` when the var is unset. */ | ||
@@ -159,2 +99,15 @@ const withDefault = (validator, defaultValue, spec = {}) => validator({ ...spec, default: defaultValue }); | ||
| /** | ||
| * Parse a comma-separated env value into a trimmed, non-empty string list; | ||
| * unset/blank => undefined. | ||
| */ | ||
| const parseEnvList = (val) => { | ||
| if (!val) | ||
| return undefined; | ||
| return val | ||
| .split(',') | ||
| .map((s) => s.trim()) | ||
| .filter(Boolean); | ||
| }; | ||
| exports.parseEnvList = parseEnvList; | ||
| /** | ||
| * Lenient boolean validator. Unlike envalid's built-in `bool` (which rejects | ||
@@ -173,3 +126,3 @@ * e.g. `TRUE`/`yes`), this accepts `true`/`1`/`yes` case-insensitively. Safe to | ||
| /** | ||
| * Validate environment variables with secret file support | ||
| * Validate environment variables | ||
| * | ||
@@ -199,10 +152,5 @@ * @param inputEnv - The environment object (usually process.env) | ||
| const varEnv = cleanEnv(inputEnv, vars); | ||
| // Read secrets from files | ||
| const _secrets = secretEnv(inputEnv, secrets); | ||
| // Second pass: validate secrets with file values merged in | ||
| // Include inputEnv first so env vars (e.g., Kubernetes secretKeyRef) are available, | ||
| // then varEnv overrides, then file-based secrets have highest priority | ||
| const mergedEnv = { ...inputEnv, ...varEnv, ..._secrets }; | ||
| const mergedEnv = { ...inputEnv, ...varEnv }; | ||
| return cleanEnv(mergedEnv, { ...secrets, ...vars }); | ||
| }; | ||
| exports.env = env; |
+3
-6
| { | ||
| "name": "12factor-env", | ||
| "version": "1.17.0", | ||
| "version": "1.17.1", | ||
| "author": "Constructive <developers@constructive.io>", | ||
| "description": "Environment variable validation with secret file support for 12-factor apps", | ||
| "description": "Environment variable validation for 12-factor apps", | ||
| "main": "index.js", | ||
@@ -44,8 +44,5 @@ "module": "esm/index.js", | ||
| "12factor", | ||
| "secrets", | ||
| "kubernetes", | ||
| "docker", | ||
| "constructive" | ||
| ], | ||
| "gitHead": "ff6c442b789cbe8803f33036110f6e09124e65df" | ||
| "gitHead": "e53a570a4a70987e82dc074bd73501971fd82828" | ||
| } |
+2
-78
@@ -19,5 +19,5 @@ # 12factor-env | ||
| > Environment variable validation with secret file support for 12-factor apps | ||
| > Environment variable validation for 12-factor apps | ||
| A TypeScript library for validating environment variables with built-in support for Docker/Kubernetes secret files. Built on top of [envalid](https://github.com/af/envalid) with additional features for reading secrets from files. | ||
| A TypeScript library for validating environment variables. Built on top of [envalid](https://github.com/af/envalid). | ||
@@ -119,45 +119,2 @@ ## Installation | ||
| ## Secret File Support | ||
| This library supports reading secrets from files, which is useful for Docker secrets and Kubernetes secrets that are mounted as files. | ||
| ### Direct Secret Files | ||
| Secrets can be read from `/run/secrets/` (or a custom path via `ENV_SECRETS_PATH`): | ||
| ```ts | ||
| import { env, str } from '12factor-env'; | ||
| // If /run/secrets/DATABASE_PASSWORD exists, it will be read automatically | ||
| const config = env(process.env, { | ||
| DATABASE_PASSWORD: str() | ||
| }); | ||
| ``` | ||
| ### _FILE Suffix Pattern | ||
| You can also use the `_FILE` suffix pattern commonly used with Docker: | ||
| ```bash | ||
| # Set the path to the secret file | ||
| export DATABASE_PASSWORD_FILE=/run/secrets/db-password | ||
| ``` | ||
| ```ts | ||
| import { env, str } from '12factor-env'; | ||
| // Will read from the file specified in DATABASE_PASSWORD_FILE | ||
| const config = env(process.env, { | ||
| DATABASE_PASSWORD: str() | ||
| }); | ||
| ``` | ||
| ### Custom Secrets Path | ||
| Set `ENV_SECRETS_PATH` to change the default secrets directory: | ||
| ```bash | ||
| export ENV_SECRETS_PATH=/custom/secrets/path | ||
| ``` | ||
| ## Validators | ||
@@ -201,35 +158,2 @@ | ||
| ### `secret(envFile)` | ||
| Create a validator for a secret file: | ||
| ```ts | ||
| import { env, secret } from '12factor-env'; | ||
| const config = env(process.env, { | ||
| DB_PASSWORD: secret('DATABASE_PASSWORD') | ||
| }); | ||
| ``` | ||
| ### `getSecret(name)` | ||
| Read a secret from a file: | ||
| ```ts | ||
| import { getSecret } from '12factor-env'; | ||
| const password = getSecret('DATABASE_PASSWORD'); | ||
| ``` | ||
| ### `secretPath(name)` | ||
| Resolve the full path to a secret file: | ||
| ```ts | ||
| import { secretPath } from '12factor-env'; | ||
| const path = secretPath('DATABASE_PASSWORD'); | ||
| // Returns: /run/secrets/DATABASE_PASSWORD | ||
| ``` | ||
| ## Re-exports from envalid | ||
@@ -236,0 +160,0 @@ |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
10
-28.57%35830
-17.07%471
-22.79%238
-24.2%