feat(hosted): add the provider-key vault
Merged
DA
2mo ago+2176+2176+0123456789012345678901234567890123456789-7-7-0123456789
Trust posture
- Encrypted at rest: AES-256-GCM with a server-only secret (
PROVIDER_KEY_ENCRYPTION_SECRET, 64-hex-char / 32-byte key). Random nonce per write; auth tag stored. No plaintext key ever reaches a database column. - No read-back endpoint: there is deliberately no API that decrypts or returns a key. The only consumer will be a future server-side observation PR.
- Validated provider set:
VAULT_PROVIDER_IDinpackages/hosteddefines the closed set of key-scoped cloud providers accepted by the store endpoint (copilot,cursor,devin,jules,replicas). Unknown providers are refused 400. The set satisfiesRecord<string, ProviderId>from@sidecar/session, enforced viasatisfies. - Cascade on delete: the
provider_keytable hasuser_id → user.id ON DELETE cascade, so the existingDELETE /api/account/deletehandler automatically removes vault rows when it deletes the user row. No handler change needed. - Kill switch: all three vault endpoints require
PROVIDER_KEY_ENCRYPTION_SECRET. If the env var is absent or blank, every endpoint answers 503 — same posture as the OpenAI endpoints. A Preview deployment with no secret has no working vault and cannot accidentally store plaintext.
Env var
PROVIDER_KEY_ENCRYPTION_SECRET — 64 hex characters (32 bytes). Generate with:
openssl rand -hex 32
Documented in apps/web/server/README.md alongside the other hosted secrets.
PRIVACY.md
A new "Provider API keys (server-side vault)" paragraph under "What we collect" describes: encrypted at rest, no read-back, server-side use ships separately, deleted by the user or with the account. The "Storage" and "Your choices" sections are updated to match.
What ships in this PR
Schema, migration, encryption module, three handler functions (store, list, delete), two API route files, wire contract additions in @sidecar/hosted, and a full test suite. No observation code, no key use, no desktop or iOS changes.
Automated visual evidence
Download the deterministic macOS evidence · workflow run
- Commit:
2b95764420cf70b0072a9edeb28cb3d05f5c52bc - Scenario:
smoke - Physical-notch check: not performed by CI