Phase 2 cutover: dual-read/dual-write for envelope-encrypted secrets
From the PR description
Wires the Phase 2 crypto modules into the request path. After this lands and the backfill runs, every read prefers ciphertext (with plaintext fallback for un-backfilled rows) and every write seals the value under the user's DEK alongside the legacy plaintext column. The legacy columns stay populated during the cutover soak so a rollback to a v6/v7 image remains safe; migration 002 drops them once we're comfortable.
Call sites:
- lib/userSettings.ts (getUserApiKeys, getUserModelSettings) - select both column pairs, decrypt the _ct if present, else fall back to the plaintext column.
- routes/user.ts - PUT /user/api-keys/:provider seals the value and writes to BOTH dbField and dbField_ct. GET /user/api-keys/status returns Boolean(plaintext || ct).
- routes/workflows.ts - four touch points:
- resolveWorkflowAccess: lookup by shared_with_email_hmac.
- GET /workflows shared lookup: same swap. - GET /workflows/:id/shares: select both columns and decrypt _ct when present; response shape unchanged. - POST /workflows/:id/share: write all three columns (shared_with_email, _ct, _hmac); upsert onConflict swapped to (workflow_id, shared_with_email_hmac).
- lib/chatTools.ts (buildWorkflowStore): same HMAC-index swap.
- index.ts: in production, assertKmsConfigured() + EMAIL_HMAC_PEPPER presence check at startup so misconfigured deploys fail fast rather than 500-ing at the first read or write.
Supporting changes in lib/crypto/migrate.ts:
- Export byteaToBuffer for call-site decoding of Supabase bytea reads.
- Add getTenantCrypto() - a process-wide TenantCrypto singleton so the in-process DEK cache amortises KMS Decrypts across requests. Tests keep using tenantCrypto(fakeDb) directly.
Plus scripts/backfill_envelope.ts: the one-shot backfill that walks user_profiles and workflow_shares, mints DEKs as needed, and writes the _ct (and _hmac for shares) columns for any rows that still lack them. Idempotent. Runs on mike-builder with KMS_KEY_ID + EMAIL_HMAC_PEPPER set, against the same Supabase project as prod.
86/86 backend tests still passing; no test changes needed since the existing crypto-module tests cover the round-trip and the call-site changes are dual-path with the plaintext fallback exercised by the existing read paths.
Our analysis
Cut over the request path to envelope-encrypted columns — 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-6.md from
inside the repo you want the changes in.