File size: 4,073 Bytes
9e4583c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/**
 * Secrets Validator β€” FASE-01 Security Hardening
 *
 * Validates that required secrets are configured with strong values.
 * Called during server initialization (fail-fast on missing or weak secrets).
 *
 * @module secretsValidator
 */

const KNOWN_WEAK_SECRETS = [
  "omniroute-default-secret-change-me",
  "change-me-to-a-long-random-secret",
  "endpoint-proxy-api-key-secret",
  "change-me-storage-encryption-key",
  "your-secret-here",
  "secret",
  "password",
  "changeme",
];

/**
 * @typedef {Object} SecretRule
 * @property {string} name - Environment variable name
 * @property {number} minLength - Minimum acceptable length
 * @property {boolean} required - Whether the secret is required for startup
 * @property {string} description - Human-readable description
 * @property {string} generateHint - Command to generate a strong value
 */

/** @type {SecretRule[]} */
const SECRET_RULES = [
  {
    name: "JWT_SECRET",
    minLength: 32,
    required: true,
    description: "JWT signing secret for dashboard authentication",
    generateHint: "openssl rand -base64 48",
  },
  {
    name: "API_KEY_SECRET",
    minLength: 16,
    required: true,
    description: "HMAC secret for API key CRC generation",
    generateHint: "openssl rand -hex 32",
  },
];

/**
 * @typedef {Object} ValidationResult
 * @property {boolean} valid
 * @property {Array<{name: string, issue: string, hint: string}>} errors
 * @property {Array<{name: string, issue: string}>} warnings
 */

/**
 * Validate all required secrets.
 * @returns {ValidationResult}
 */
export function validateSecrets() {
  const errors = [];
  const warnings = [];

  for (const rule of SECRET_RULES) {
    const value = process.env[rule.name];

    // Missing entirely
    if (!value || value.trim() === "") {
      if (rule.required) {
        errors.push({
          name: rule.name,
          issue: `Required environment variable "${rule.name}" is not set.`,
          hint: `Generate with: ${rule.generateHint}`,
        });
      }
      continue;
    }

    // Too short
    if (value.length < rule.minLength) {
      errors.push({
        name: rule.name,
        issue: `"${rule.name}" is too short (${value.length} chars, minimum ${rule.minLength}).`,
        hint: `Generate with: ${rule.generateHint}`,
      });
      continue;
    }

    // Known weak value
    if (KNOWN_WEAK_SECRETS.includes(value.toLowerCase())) {
      warnings.push({
        name: rule.name,
        issue: `"${rule.name}" appears to use a default/weak value. Please generate a strong secret.`,
      });
    }
  }

  return {
    valid: errors.length === 0,
    errors,
    warnings,
  };
}

/**
 * Validate secrets and terminate process if critical ones are missing.
 * Should be called during server initialization (fail-fast).
 * @param {object} [logger] - Optional logger (defaults to console)
 */
export function enforceSecrets(logger = console) {
  const result = validateSecrets();

  // Print warnings (non-fatal)
  for (const w of result.warnings) {
    logger.warn(`⚠️  [SECURITY] ${w.issue}`);
  }

  // If there are errors, print them and exit
  if (!result.valid) {
    logger.error("");
    logger.error("═══════════════════════════════════════════════════");
    logger.error("  ❌  SECURITY: Missing required secrets");
    logger.error("═══════════════════════════════════════════════════");
    for (const e of result.errors) {
      logger.error(`  β€’ ${e.issue}`);
      logger.error(`    β†’ ${e.hint}`);
    }
    logger.error("");
    logger.error("  Set these in your .env file or environment.");
    logger.error("  See .env.example for reference.");
    logger.error("═══════════════════════════════════════════════════");
    logger.error("");
    process.exit(1);
  }
}