fix: require dedicated secrets for download signing and API key encryption

🟢 open · #74 · willchen96/mike ← bmersereau/mike · opened 14d ago by bmersereau · +1,443-21 across 8 files · ↗ on GitHub

From the PR description

Summary

  • Remove SUPABASE_SECRET_KEY fallback from downloadTokens.getSecret() - DOWNLOAD_SIGNING_SECRET is now required
  • Remove three-way fallback from userApiKeys.encryptionKey() - USER_API_KEYS_ENCRYPTION_SECRET is now required
  • Add assertSecretIsolation() called at server startup to enforce presence, uniqueness, and cross-secret isolation of each secret
  • Updates JSDoc comment in downloadTokens.ts to reflect the TTL-based expiration added in PR #77

Closes #66 Closes #82 Closes #83 Closes #88

Changes

  • backend/src/lib/downloadTokens.ts - getSecret() reads only DOWNLOAD_SIGNING_SECRET; JSDoc updated to reference configurable TTL
  • backend/src/lib/userApiKeys.ts - encryptionKey() reads only USER_API_KEYS_ENCRYPTION_SECRET
  • backend/src/lib/startup.ts - new assertSecretIsolation() validates at boot: missing vars throw, secrets matching SUPABASE_SECRET_KEY throw, and DOWNLOAD_SIGNING_SECRET === USER_API_KEYS_ENCRYPTION_SECRET throws
  • backend/src/index.ts - call assertSecretIsolation() before the server starts
  • backend/vitest.config.ts - vitest config excluding dist/
  • backend/src/lib/__tests__/secretIsolation.test.ts - 11 vitest unit tests covering all paths

Test plan

  • npm test passes (11/11) in backend/
  • npm run build passes with no TypeScript errors
  • .env must be updated before restarting backend (see migration note below)

⚠️ Migration note

Before restarting the backend after this change, ensure your environment has:

DOWNLOAD_SIGNING_SECRET=<dedicated random value>
USER_API_KEYS_ENCRYPTION_SECRET=<dedicated random value>

Generate fresh values with openssl rand -hex 32.

If you previously had API_KEYS_ENCRYPTION_SECRET set (an undocumented legacy fallback that was removed in this PR): set USER_API_KEYS_ENCRYPTION_SECRET to that same value. Do not generate a new value - doing so will change the AES key and make all stored user API keys in the database unreadable.

Our analysis

Make per-purpose secrets mandatory and enforce isolation at boot — read the full analysis →

Think the analysis missed something the PR description covers?

Capture this PR into my fork

Download a Markdown prompt that tells Claude how to port every commit in this PR into your working tree. Run it via claude -p < capture-pull-74.md from inside the repo you want the changes in.

⬇ Download capture-pull-74.md