BYOK Anthropic and Gemini keys stored encrypted alongside Case.dev gateway

kveton added a second credential surface to the CaseMark fork. Users can now route Anthropic and Gemini calls directly with their own keys instead of going through the Case.dev model gateway. The credential management sits in a new `provider_api_credentials` Postgres table with encrypted storage, live validation, and soft-delete on removal.

integrationsecurity

The bulk of the work lands in backend/src/lib/providerCredentials.ts (385 lines). Keys are encrypted with encryptCaseApiKey/decryptCaseApiKey - the same AES-256-GCM path used for Case credentials - and validated on save by calling the provider's /v1/models endpoint. The stored row tracks last4, verified_at, last_checked_at, capabilities: { llm, model_count }, and status (verified/unverified/invalid). Migration 009_provider_credentials.sql creates the table; the review pass (2032ca81) added revoked_at and changed key removal from a hard delete to a soft-delete update that zeroes the key material.

A MIKE_ALLOW_SERVER_PROVIDER_KEY_FALLBACK env var lets non-production environments fall back to keys in .env. The initial commit defaulted this to true, which the review pass corrected: production now unconditionally returns false regardless of the env var.

On the frontend side, provider availability logic was consolidated into modelAvailability.ts. Four components (ChatInput, TRChatPanel, TabularReviewView, useSelectedModel) had each been computing this independently; the refactor gives them a shared providerCredentialState() function. The Account > Models page was reorganized into Case.dev, Model Providers, Model Defaults, and Storage sections.

The /case-models endpoint picks up a provider_errors field in its response when a live model catalog call fails, logging a fallback to the static model list with a generic "Provider model catalog unavailable; using static fallback" message rather than forwarding the raw error detail to clients.

So what Worth a look if you want encrypted per-user provider keys without holding them in plaintext. The encryption reuse, the live validation pattern, and the migration shape are reasonably self-contained and don't require Case.dev elsewhere. Two things to check before copying: `serverProviderFallbackAllowed()` has production semantics baked in (always false), which is correct but may conflict with a different deployment structure. And clearing a key does a soft-delete, not a hard delete, so your data retention policy needs to account for zeroed-out rows sticking around.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

5 commits from CaseMark/mikeoss-casedotdev, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
c0ed073a Add BYOK model provider settings kveton 2026-05-04 ↗ GitHub
2032ca81 Address BYOK review feedback kveton 2026-05-04 ↗ GitHub
b3098e8b Tighten BYOK review fixes kveton 2026-05-04 ↗ GitHub
54f5b8b8 Polish BYOK provider feedback kveton 2026-05-04 ↗ GitHub
3967ef8c Merge pull request #2 from CaseMark/codex/settings-byok-storage Scott Kveton 2026-05-04 ↗ GitHub
Add BYOK model provider settings

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-31.md from inside the repo you want the changes in.

⬇ Download capture-thread-31.md