Skip to content

feat: opt-in encryption at rest for kv and episodic - #67

Merged
mkorbi merged 1 commit into
mainfrom
feat/encryption-at-rest-mindd
Aug 22, 2026
Merged

feat: opt-in encryption at rest for kv and episodic#67
mkorbi merged 1 commit into
mainfrom
feat/encryption-at-rest-mindd

Conversation

@mkorbi

@mkorbi mkorbi commented Aug 22, 2026

Copy link
Copy Markdown
Member

Adds opt-in encryption at rest for the kv and episodic blocks.

This is the previously-developed encryption work, re-applied on top of the mindD rename (#65) with module paths, the capability header and the env prefix mechanically translated. It now sits directly on main — not stacked.

Design

A Driver decorator (internal/<block>/encrypted.go) applied per-namespace at the Bind seam in main.go. No service, driver-interface or schema changes, so it works identically over the memory, Postgres and Redis backends.

  • Global ordered keyring encryption.keys — first key is active, all remain decryption candidates, so rotation is prepend-then-retire.
  • Secrets are env-only; per-namespace opt-in via encrypt: true. Blocks that don't support it reject encrypt at config validation.
  • AAD is varint-length-prefixed (block, namespace[, kv key]), so ciphertext cannot be relocated across key, namespace or block. Episodic binds only to namespace, because ids/cursors are assigned after sealing.
  • allow_plaintext_reads is the in-place migration escape hatch.
  • Also fixes DecodeSecret accepting any length — an unset env var previously yielded a 0-byte "key".

Deliberately out of scope

Documented in the concept page:

  • semantic — sealing content would silently break the sparse/hybrid lanes (there's a GIN index on to_tsvector(content)), and Upsert mutates the caller's slice with ids the service reads back out.
  • artifact — streaming plus plaintext byte-range reads need chunked AEAD framing, and Size/SHA256 are computed over plaintext by the service.
  • graph / lease — negligible payload.

Decorator trap worth knowing

Wrapping a Driver hides optional capability interfaces. namespaceSizer.Size must be forwarded for all six blocks (semantic's has no namespace arg), as must artifact's metaPatcher.PatchMeta — otherwise metrics/admin introspection and artifact size/sha silently break.

Verification

Run against this branch after the rebase onto current main:

  • go build ./..., go vet ./... clean
  • go test -race ./... — all pass
  • golangci-lint run0 issues

The build-tagged Postgres integration tests were NOT run in this environment (the container runtime backing testcontainers was unavailable). That is worth stating plainly rather than implying full coverage. The reason it is nonetheless low-risk: the encryption logic itself is unchanged from the version previously verified against a real Postgres, and the rename it was re-applied over touched only import lines in the Postgres drivers — the rename commit contains zero DDL/DML, so no table, column, index or migration name moved. Re-running those tests before merge is still the right call if a runtime is available.

🤖 Generated with Claude Code

Re-applied on top of the mindD rename. Original commit 72449f8 on
feat/encryption-at-rest, with module paths, the capability header and
env prefix mechanically translated to the new names.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mkorbi
mkorbi merged commit 14f6ca8 into main Aug 22, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant