OpenPlaud Docs
Reference

Encryption at rest

AES-256-GCM envelope encryption for sensitive database fields.

OpenPlaud encrypts user content fields in the database with AES-256-GCM, keyed off the ENCRYPTION_KEY environment variable that the runtime already requires.

This is server-held-key envelope encryption. It is not zero-knowledge.

What is encrypted

ColumnTypeNotes
recordings.filenametextOften carries topic info (e.g. AI-generated titles)
transcriptions.texttextFull transcript
ai_enhancements.summarytextLLM summary
ai_enhancements.key_pointsjsonbStored as { "c": "v1:..." } envelope
ai_enhancements.action_itemsjsonbSame envelope shape
user_settings.summary_promptjsonbCustom prompt configs may carry user context
user_settings.title_generation_promptjsonbSame

Pre-existing encryption (unchanged):

  • plaud_connections.bearer_token
  • api_credentials.api_key
  • storage_config.s3_config

Out of scope for this layer:

  • Audio files in storage (local FS / S3). Object-level encryption is a separate, heavier change. For S3, prefer server-side encryption at the bucket level today; revisit per-object app-layer encryption later.
  • Search. No full-text search on transcripts is implemented today, so encrypting text causes no regression. If search lands later, it will need a tokenized HMAC index — not this PR.

What this protects against

  • Stolen DB backups or snapshots
  • Read-replica access without app-server access
  • A SQL-injection that reads but does not execute application code
  • Operators with DB-only access (e.g. via an admin console) cannot read content without also having the app-server key

What this does not protect against

  • A compromised application server. The server holds the key and decrypts content at request time so it can run AI on it. This is unavoidable while server-side transcription and summarization are part of the product.
  • A compromised ENCRYPTION_KEY. Treat the key like a database master credential.
  • A compromised AI provider. Plaintext is sent to whichever transcription / enhancement provider the user configured. That trust boundary is the user's choice and is independent of this layer.

If you need to keep plaintext audio and transcripts off third-party infrastructure entirely, run a local AI provider (Ollama or LM Studio) on the same machine as your OpenPlaud instance. The server still holds the encryption key, but no plaintext leaves the box.

Key management

ENCRYPTION_KEY must be a 64-character hex string (32 bytes). Generate one with:

node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))"

Losing the key makes encrypted content unrecoverable. Backup the key separately from the database.

Backup files produced by /api/backup are plaintext by design — they are the user's own export. Store them with the same care you'd give the unencrypted database.

Versioning and key rotation

New ciphertext written by this layer is prefixed with v1:. Older formats are still readable:

  • v1:iv:tag:hex — current. Identifies the key/version that produced it.
  • iv:tag:hex (no prefix) — pre-existing format used for Plaud bearer tokens and AI keys. Read path tolerates it.
  • Anything else — treated as legacy plaintext and returned verbatim. This is the deploy → backfill compatibility window.

Key rotation is not implemented yet. The v1: prefix exists so a future rotation pass can identify which rows need re-encrypting.

Backfill

Pre-rollout rows stay plaintext until rewritten. The read path tolerates both shapes during this window. To eagerly encrypt existing rows:

Docker / self-host (the supported path): the script is bundled into the image at /app/encrypt-backfill.js.

# Report what would change (no writes)
docker compose exec app bun encrypt-backfill.js --dry-run

# Apply
docker compose exec app bun encrypt-backfill.js

From a source checkout (development):

bun scripts/encrypt-backfill.ts --dry-run
bun scripts/encrypt-backfill.ts

The script is idempotent (skips rows already in v1: form), batched, and safe to interrupt and resume.

Edit on GitHub

Last updated on

On this page