feat(storage): at-rest encryption for node content via key providers - #59
Merged
Conversation
migration v5's columns are now wired to a real key provider:
- KeyProvider interface: versioned master key material; EnvKeyProvider
reads it from a process environment variable (no secret on disk)
- NodeCipher: AES-256-GCM with per-version keys derived via HKDF-SHA256;
ciphertexts carry 'yaad.aes256gcm.v{n}.' prefix, random nonce, and the
key version as authenticated data (relabeling/tampering fails reads)
- Store.EnableEncryption(opts in after NewStore) threads the cipher
through every node read/write path, txStore included, and bookkeeps
nodes.encrypted / encryption_key_version
- node_versions history encrypts alongside live content
- legacy plaintext rows stay readable after enabling encryption and are
progressively re-encrypted on update
- SearchNodes falls back to an in-memory token scan over decrypted
content when the cipher is active (FTS5 indexes ciphertext)
- default behaviour unchanged: no provider = plaintext, columns false/0
- go directive -> 1.26.6 (stdlib CVEs per govulncheck)
tests: round trip, tamper + wrong-key rejection, env provider, legacy
plaintext boundary, tx path, FTS fallback; full suite + race green
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 2 item 4: migration v5's
encrypted/encryption_key_versioncolumns are now wired to a real key provider.Design (app-layer, not SQLCipher — the pure-Go modernc.org/sqlite driver has no encryption hooks; a separate driver is out of scope):
KeyProviderinterface: versioned master key material. Ship aNewEnvKeyProvider(var)that sources the secret from the process environment — nothing touches disk.NodeCipher: AES-256-GCM; per-version keys derived via HKDF-SHA256 (deterministic, never stored). Ciphertext formatyaad.aes256gcm.v{n}.<base64url(nonce||seal)>with random nonces (no determinism leak) and the key version bound as AAD — relabeled ciphertexts fail authentication.Store.EnableEncryption(provider)afterNewStore; idempotent, eagerly validated (missing env var fails at setup, not first write). Threads through every node content read/write path (createNodeQ,updateNodeQ,updateNodeContentQ, scans, batch, neighbors, by-hash, versions) plustxStorevia the shared*Qhelpers. Bookkeeping columns set in lockstep.node_versionshistory encrypts;RollbackToVersion/DiffVersionsdecrypt before use (no double-encryption).Tests: round trip + nonce randomization, tamper/wrong-key/relabel rejection, env provider, legacy-plaintext boundary + progressive re-encrypt, tx path, FTS fallback. Full
go test ./...+-racegreen; lint/fmt clean on linux/amd64, linux/arm64, windows/amd64.godirective bumped to 1.26.6 (stdlib CVEs per govulncheck).