Fix MCP crash when DB key exists without NEXUS_MASTER_KEY

KeyResolver now wraps getApiKey in try/catch and falls back to env instead of
crashing startup. ProvidersStore.getApiKey returns undefined when the cipher is
disabled rather than throwing. Server starts cleanly with env-only, and uses the
encrypted DB key when the master key is present.
This commit is contained in:
OpenCode
2026-08-17 18:02:35 +07:00
parent 679e56424b
commit 6ff2b27556
2 changed files with 18 additions and 2 deletions
@@ -133,12 +133,17 @@ export class ProvidersStore {
this.db.prepare('DELETE FROM providers WHERE name = ?').run(name); this.db.prepare('DELETE FROM providers WHERE name = ?').run(name);
} }
/** Return the decrypted API key for a provider, or undefined. */ /**
* Return the decrypted API key for a provider, or undefined.
* Returns undefined (instead of throwing) when no master key is configured,
* so callers can gracefully fall back to env keys.
*/
getApiKey(name: string): string | undefined { getApiKey(name: string): string | undefined {
const row = this.db.prepare('SELECT api_key_enc FROM providers WHERE name = ?').get(name) as const row = this.db.prepare('SELECT api_key_enc FROM providers WHERE name = ?').get(name) as
| { api_key_enc: string | null } | { api_key_enc: string | null }
| undefined; | undefined;
if (!row?.api_key_enc) return undefined; if (!row?.api_key_enc) return undefined;
if (!this.cipher.enabled) return undefined;
return this.cipher.decrypt(row.api_key_enc); return this.cipher.decrypt(row.api_key_enc);
} }
+12 -1
View File
@@ -45,7 +45,18 @@ export class KeyResolver {
resolve(providerName: string, defaultBaseUrl: string): ResolvedProvider { resolve(providerName: string, defaultBaseUrl: string): ResolvedProvider {
const dbRow = this.store?.get(providerName); const dbRow = this.store?.get(providerName);
const envKey = this.envKeyFor(providerName); const envKey = this.envKeyFor(providerName);
const dbKey = this.store?.getApiKey(providerName); // DB key may be unreadable (no master key / rotated key). Degrade gracefully:
// fall back to env instead of crashing startup.
let dbKey: string | undefined;
try {
dbKey = this.store?.getApiKey(providerName);
} catch (err) {
log.warn(
`could not read stored key for "${providerName}" (falling back to env)`,
err instanceof Error ? err.message : String(err)
);
dbKey = undefined;
}
const apiKey = envKey ?? dbKey; const apiKey = envKey ?? dbKey;
const keySource: ResolvedProvider['keySource'] = envKey ? 'env' : dbKey ? 'db' : 'none'; const keySource: ResolvedProvider['keySource'] = envKey ? 'env' : dbKey ? 'db' : 'none';