feat(#359): Encrypt LLM provider API keys in database

Implemented transparent encryption/decryption of LLM provider API keys
stored in llm_provider_instances.config JSON field using OpenBao Transit
encryption.

Implementation:
- Created llm-encryption.middleware.ts with encryption/decryption logic
- Auto-detects format (vault:v1: vs plaintext) for backward compatibility
- Idempotent encryption prevents double-encryption
- Registered middleware in PrismaService
- Created data migration script for active encryption
- Added migrate:encrypt-llm-keys command to package.json

Tests:
- 14 comprehensive unit tests
- 90.76% code coverage (exceeds 85% requirement)
- Tests create, read, update, upsert operations
- Tests error handling and backward compatibility

Migration:
- Lazy migration: New keys encrypted, old keys work until re-saved
- Active migration: pnpm --filter @mosaic/api migrate:encrypt-llm-keys
- No schema changes required
- Zero downtime

Security:
- Uses TransitKey.LLM_CONFIG from OpenBao Transit
- Keys never touch disk in plaintext (in-memory only)
- Transparent to LlmManagerService and providers
- Follows proven pattern from account-encryption.middleware.ts

Files:
- apps/api/src/prisma/llm-encryption.middleware.ts (new)
- apps/api/src/prisma/llm-encryption.middleware.spec.ts (new)
- apps/api/scripts/encrypt-llm-keys.ts (new)
- apps/api/prisma/migrations/20260207_encrypt_llm_api_keys/ (new)
- apps/api/src/prisma/prisma.service.ts (modified)
- apps/api/package.json (modified)

Note: The migration script (encrypt-llm-keys.ts) is not included in
tsconfig.json to avoid rootDir conflicts. It's executed via tsx which
handles TypeScript directly.

Refs #359

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-07 16:49:37 -06:00
parent 864c23dc94
commit aa2ee5aea3
7 changed files with 1145 additions and 1 deletions

View File

@@ -2,6 +2,7 @@ import { Injectable, Logger, OnModuleDestroy, OnModuleInit } from "@nestjs/commo
import { PrismaClient } from "@prisma/client";
import { VaultService } from "../vault/vault.service";
import { registerAccountEncryptionMiddleware } from "./account-encryption.middleware";
import { registerLlmEncryptionMiddleware } from "./llm-encryption.middleware";
/**
* Prisma service that manages database connection lifecycle
@@ -33,6 +34,10 @@ export class PrismaService extends PrismaClient implements OnModuleInit, OnModul
// VaultService provides OpenBao Transit encryption with AES-256-GCM fallback
registerAccountEncryptionMiddleware(this, this.vaultService);
this.logger.log("Account encryption middleware registered");
// Register LLM provider API key encryption middleware
registerLlmEncryptionMiddleware(this, this.vaultService);
this.logger.log("LLM encryption middleware registered");
} catch (error) {
this.logger.error("Failed to connect to database", error);
throw error;