Back to home
Security posture

Where your keys live — and how they're protected.

OpenBooks is bring-your-own-keys and self-hosted. Before you paste a Plaid secret, a Stripe restricted key, or an AI provider key into your own deployment, here is exactly how that secret is handled — with file citations you can check yourself. This is a lean, honest, code-cited statement, not a marketing trust page.

01

Credentials are encrypted at rest

Plaid access tokens, Stripe restricted keys, Plunk keys and your AI provider key are encrypted with AES-GCM before they touch the database. The key is derived per-ciphertext via HKDF-SHA-256 from a deployment secret and is never written to the database — no key, no stored credential.

convex/secretBox.ts · encryptSecret() / deriveHkdfKey()
grep -n "AES-GCM\|deriveHkdfKey" convex/secretBox.ts
02

Secrets are never returned to the client

Saving or listing a credential returns only redacted metadata — a one-way fingerprint, a short key preview (sk_live_…1234) and a status. The plaintext key or token never leaves the server. Resolved plaintext exists only inside server-side actions.

convex/credentials.ts · saveCredential() return shape, credentialStatus(), maskKeyPreview()
grep -n "keyPreview\|fingerprint\|maskKeyPreview\|credentialStatus" convex/credentials.ts
03

Live connectors — with a required HTTPS redirect

Live Plaid (development/production) and live Stripe keys are supported locally and in self-host; there is no sandbox/test-only ban. The retained guarantee is that live connectors need a stable HTTPS origin for OAuth redirects and webhooks — an http:// origin cannot safely receive a live bank or payment callback.

convex/connections.ts · stripeRedirectUri() HTTPS guard (~line 264)
grep -an "requires an HTTPS\|sk_live_" convex/connections.ts
04

Authorization is re-checked server-side

Authorization is never trusted from the client. Every query, mutation and action re-checks the caller's workspace/entity permission on the server before reading or writing — dozens of backend modules call a shared authz helper.

convex/authz.ts · requireWorkspacePermission / requireAnyWorkspacePermission
grep -rl "requireWorkspacePermission\|requireUserId" convex/*.ts | grep -v test | wc -l
05

No secret or PII is committed to git

The repository never contains a real secret or private financial record. .gitignore ignores .env and .env.* and allowlists only .env.example (placeholders). A secret-scan gate (planned, E13-T8) fails the build on any real key shape in tracked docs or pages.

.gitignore · docs/security/secrets.md · pnpm scan:secrets (planned)
git ls-files | grep -E '^\.env'  # prints only .env.example

What this does not claim

  • No third-party security audit has been performed yet.
  • Anyone with read access to your deployment's environment variables can read the encryption key and decrypt stored credentials — protect your Convex/Vercel deployment env like the secret it is.
  • This is a v1 honest statement, not a full threat model.
Found a security issue? Email security@openbooks.dev and give us a reasonable window to fix before public disclosure.