Replace plaintext .env secrets with Vault KV storage using AppRole auth, with graceful fallback to env vars during migration. ## Acceptance Criteria - [ ] `JWT_SECRET`, `SUPABASE_SERVICE_KEY`, and `RESEND_API_KEY` are stored in Vault KV - [ ] Server authenticates to Vault via AppRole (not root token) - [ ] Vault policy restricts server access to only required paths - [ ] `loadSecretsFromVault()` runs before NestJS bootstrap - [ ] Graceful fallback to env vars when `VAULT_FALLBACK_TO_ENV=true` - [ ] Hard failure when Vault is unavailable and no fallback is configured - [ ] `vault-init` container is idempotent (safe to run multiple times) - [ ] Vault root token is no longer required for server operation - [ ] No plaintext secrets in Docker logs - [ ] Signing flow works end-to-end with Vault-sourced secrets ## Tasks - [ ] Create `docker/vault/guardian-policy.hcl` - [ ] Create `docker/vault/init.sh` - [ ] Create `vault-secrets.ts` with `loadSecretsFromVault()` - [ ] Update `main.ts` to call `loadSecretsFromVault()` before bootstrap - [ ] Make `VAULT_TOKEN` optional in config - [ ] Add AppRole support to Vault client - [ ] Add `vault-init` service and `vault-creds` volume to docker-compose - [ ] Write unit + integration tests (Vault available, fallback, hard failure, token refresh)
Replace plaintext .env secrets with Vault KV storage using AppRole auth, with graceful fallback to env vars during migration.
Acceptance Criteria
JWT_SECRET,SUPABASE_SERVICE_KEY, andRESEND_API_KEYare stored in Vault KVloadSecretsFromVault()runs before NestJS bootstrapVAULT_FALLBACK_TO_ENV=truevault-initcontainer is idempotent (safe to run multiple times)Tasks
docker/vault/guardian-policy.hcldocker/vault/init.shvault-secrets.tswithloadSecretsFromVault()main.tsto callloadSecretsFromVault()before bootstrapVAULT_TOKENoptional in configvault-initservice andvault-credsvolume to docker-compose