From 673961580cf639d32d18a14c3117786ca4a8b91a Mon Sep 17 00:00:00 2001 From: Dorian Date: Sun, 8 Mar 2026 10:43:30 +0000 Subject: [PATCH] fix: defer wallet encryption key check so server starts without it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move crypto key init from module-level throw to lazy getKey() — only errors when encrypt/decrypt are actually called. Downgrade env var check in index.ts from fatal exit to warning. Co-Authored-By: Claude Opus 4.6 --- server/src/engine/crypto.ts | 29 ++++++++++++++++++----------- server/src/index.ts | 9 ++++----- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/server/src/engine/crypto.ts b/server/src/engine/crypto.ts index 7408d5d..a0fda27 100644 --- a/server/src/engine/crypto.ts +++ b/server/src/engine/crypto.ts @@ -1,31 +1,38 @@ import { createCipheriv, createDecipheriv, randomBytes } from 'crypto' -const ENCRYPTION_KEY_HEX = process.env.BOTFIGHTS_WALLET_ENCRYPTION_KEY -let encryptionKey: Buffer +let encryptionKey: Buffer | null = null -if (ENCRYPTION_KEY_HEX) { - encryptionKey = Buffer.from(ENCRYPTION_KEY_HEX, 'hex') -} else if (process.env.NODE_ENV === 'production') { - throw new Error('CRITICAL: BOTFIGHTS_WALLET_ENCRYPTION_KEY not set. Cannot start in production.') -} else { - encryptionKey = randomBytes(32) - console.warn('[crypto] WARNING: No BOTFIGHTS_WALLET_ENCRYPTION_KEY set. Generated random key — wallet data will be lost on restart.') +function getKey(): Buffer { + if (encryptionKey) return encryptionKey + + const hex = process.env.BOTFIGHTS_WALLET_ENCRYPTION_KEY + if (hex) { + encryptionKey = Buffer.from(hex, 'hex') + } else if (process.env.NODE_ENV === 'production') { + throw new Error('BOTFIGHTS_WALLET_ENCRYPTION_KEY not set. Wallet operations unavailable.') + } else { + encryptionKey = randomBytes(32) + console.warn('[crypto] WARNING: No BOTFIGHTS_WALLET_ENCRYPTION_KEY set. Generated random key — wallet data will be lost on restart.') + } + return encryptionKey } export function encrypt(plaintext: string): string { + const key = getKey() const iv = randomBytes(16) - const cipher = createCipheriv('aes-256-gcm', encryptionKey, iv) + const cipher = createCipheriv('aes-256-gcm', key, iv) const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]) const authTag = cipher.getAuthTag() return iv.toString('hex') + ':' + authTag.toString('hex') + ':' + encrypted.toString('hex') } export function decrypt(ciphertext: string): string { + const key = getKey() const [ivHex, authTagHex, encryptedHex] = ciphertext.split(':') const iv = Buffer.from(ivHex, 'hex') const authTag = Buffer.from(authTagHex, 'hex') const encrypted = Buffer.from(encryptedHex, 'hex') - const decipher = createDecipheriv('aes-256-gcm', encryptionKey, iv) + const decipher = createDecipheriv('aes-256-gcm', key, iv) decipher.setAuthTag(authTag) return decipher.update(encrypted) + decipher.final('utf8') } diff --git a/server/src/index.ts b/server/src/index.ts index 48b5557..b173ccd 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -4,13 +4,12 @@ import { runMigrations } from './db/startup.js' import { seedMockBots, seedClassicBots } from './engine/mock.js' import { startBackgroundFights } from './engine/background.js' -// Production env validation +// Production env validation — warn but don't crash (wallet features degrade gracefully) if (process.env.NODE_ENV === 'production') { - const required = ['BOTFIGHTS_NWC_URL', 'BOTFIGHTS_WALLET_ENCRYPTION_KEY'] - const missing = required.filter(k => !process.env[k]) + const walletVars = ['BOTFIGHTS_NWC_URL', 'BOTFIGHTS_WALLET_ENCRYPTION_KEY'] + const missing = walletVars.filter(k => !process.env[k]) if (missing.length > 0) { - console.error(`[FATAL] Missing required env vars for production: ${missing.join(', ')}`) - process.exit(1) + console.warn(`[botfights] WARNING: Missing env vars: ${missing.join(', ')} — wallet/payment features disabled`) } }