feat(hosted): add the provider-key vault

Merged
DA
2mo ago
+2176-7

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_ID in packages/hosted defines 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 satisfies Record<string, ProviderId> from @sidecar/session, enforced via satisfies.
  • Cascade on delete: the provider_key table has user_id → user.id ON DELETE cascade, so the existing DELETE /api/account/delete handler 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.

Open in Alchemize

Automated visual evidence

Download the deterministic macOS evidence · workflow run

  • Commit: 2b95764420cf70b0072a9edeb28cb3d05f5c52bc
  • Scenario: smoke
  • Physical-notch check: not performed by CI