Load Secrets From Environment Not From Files

A config loader that keeps secret values out of committed files by resolving them from the environment and refusing to read them from disk.

JavascriptConfig Management

Code

export class SecretConfigError extends Error {
  constructor(message, details = {}) {
    super(message);
    this.name = "SecretConfigError";
    this.missing = details.missing ?? [];
    this.leaked = details.leaked ?? [];
  }
}

export function loadConfig(filePayload, secretSpecs, env = process.env) {
  if (!filePayload || typeof filePayload !== "object" || Array.isArray(filePayload)) {
    throw new SecretConfigError("file payload must be an object");
  }

  const leaked = secretSpecs
    .filter((spec) => Object.prototype.hasOwnProperty.call(filePayload, spec.key))
    .map((spec) => spec.key);
  if (leaked.length > 0) {
    throw new SecretConfigError(
      "secret keys must never appear in a committed file: " + leaked.join(", "),
      { leaked },
    );
  }

  const config = { ...filePayload };
  const missing = [];
  for (const spec of secretSpecs) {
    const value = env[spec.envVar];
    if (value === undefined || value === "") {
      missing.push(spec.envVar);
      continue;
    }
    config[spec.key] = value;
  }
  if (missing.length > 0) {
    throw new SecretConfigError("missing required secrets: " + missing.join(", "), { missing });
  }
  return config;
}