feat(crypto): BYOK envelope encryption library + local KMS (Step 6.7a) - #155
Merged
Merged
Conversation
First slice of BYOK. New KeyManager SPI (rag-core) over opaque bytes (encrypt/decrypt) + NoopKeyManager passthrough; EncryptingStorage decorator (crypto-free) that encrypts blobs on put / decrypts on get — the application seam over any Storage. EnvelopeKeyManager base (rag-backends, cryptography) does the data-key half once: a fresh AES-256-GCM DEK per payload with ctx.tenant_id bound as AAD, the small DEK wrapped/unwrapped by a subclass via the KEK. LocalKeyManager wraps with an in-process per-tenant KEK (dev/tests/air-gapped); cloud providers (6.7b) subclass and wrap via the KMS API so the KEK never leaves the customer. Per-tenant isolation via KEK-per-tenant + tenant-bound AAD (a shared KEK still can't read another tenant's blob); sealing via KeyUnavailableError (a tenant with no KEK is denied, others unaffected); tamper-evidence via the GCM auth tag. Adds EncryptionError / KeyUnavailableError + ragctl kms demo. Encryption targets chunk content / blobs at rest, NOT embedding vectors (ANN search needs plaintext vectors) — a deliberate, documented constraint. Deferred: cloud KMS providers behind [kms-*] extras + wiring EncryptingStorage into the ingest/storage path + per-tenant key config (6.7b); key rotation (6.7c). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.
Summary
First slice of Step 6.7 — BYOK (Bring Your Own Key): the envelope-encryption library + a local KMS. New ground — the only prior crypto was HMAC signing.
KeyManagerSPI (rag-core) over opaque bytes (encrypt/decrypt) +NoopKeyManager(passthrough).EncryptingStorage(rag-core, crypto-free) — aStoragedecorator that encrypts onput/ decrypts onget: the application seam over any backend.EncryptionError+KeyUnavailableError.EnvelopeKeyManagerbase (rag-backends,cryptography) does the data-key half once for every provider: a fresh AES-256-GCM DEK per payload (withctx.tenant_idbound as AAD), the small DEK wrapped/unwrapped by a subclass via the KEK.LocalKeyManagerwraps with an in-process per-tenant KEK; cloud providers (6.7b) subclass and wrap via the KMS API so the KEK never leaves the customer.KeyUnavailableError, others unaffected).cryptographylives inrag-backends;rag-corestays crypto-free (SPI + decorator + noop).ragctl kmsdemonstrates the round-trip + isolation + sealing.Scope / deferred: library + local KMS only. 6.7b — cloud KMS providers behind
[kms-*]extras + wireEncryptingStorageinto the ingest/storage path + per-tenant key config (cfg.kms+TenantConfigkey ref). 6.7c — zero-downtime key rotation. Vector encryption is out of scope by design.Tests & gates
KeyManagercontract over noop + local (round-trip, empty, health); envelope (tests/kms/test_envelope.py) — non-determinism, tamper → EncryptionError, malformed, cross-tenant blocked, shared-KEK-still-blocked, sealing on encrypt + decrypt, wrong-size KEK, one-tenant-sealed-doesn't-affect-another,EncryptingStorage(stores ciphertext + delegates key ops);ragctl kmssmoke.tests/ packages/2347 passed, 1 skipped. Green: ruff,mypy --strict(305 files), RAG001, policy-coverage, log-schema; no dist drift (no new wire types / config / endpoints in 6.7a).Documentation
Test plan
uv run pytest tests/ packages/ -m "not integration"— 2347 passed, 1 skippedmypy --strict(305 files) + RAG001 + policy-coverage + log-schema; no dist drift🤖 Generated with Claude Code