feat!: replace flat master-key vault encryption with Argon2id KEK/DEK model#343
Merged
Conversation
…model Replaces the flat FernetEncryptionWrapper + EncryptionConfig model with a proper envelope encryption scheme: - MasterSecretResolver: unified resolution order (env → file → keyring → auto-generate) under a single AUTHSOME_MASTER_KEY env var; no separate passphrase vs raw-key distinction — both go through Argon2id - DekManager: generates a random 256-bit DEK, wraps it with an Argon2id-derived KEK (AES-256-GCM), and stores the wrapped record in the KV store under __vault_meta__:__dek__ so it works with any KV backend - AesGcmEncryptionWrapper: drop-in BaseEncryptionWrapper using AES-256-GCM per-value encryption via closure; replaces FernetEncryptionWrapper - Vault: simplified — no longer owns crypto or lifecycle; receives an already-encrypted AsyncKeyValue; close() removed (caller manages store) - EncryptionConfig removed from ServerConfig and models __all__ - Health route updated to report crypto_source from Vault properties - Tests rewritten with fixtures and SimpleStore (in-memory); no DiskStore BREAKING CHANGE: existing Fernet-encrypted vaults cannot be read back; migration requires re-importing credentials. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Entire-Checkpoint: a778dfa71075
beubax
approved these changes
May 27, 2026
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.
Motivation
The previous vault used Fernet (AES-128-CBC + HMAC-SHA256) with a master key loaded directly from env, file, or keyring — a flat, single-key model where the master secret encrypted every credential blob directly. This design has two critical weaknesses:
Design
Envelope encryption — the same model used by AWS Secrets Manager, HashiCorp Vault, and GCP KMS:
Why a DEK?
The random 256-bit DEK is what actually encrypts credential blobs. The master secret only ever touches the DEK (wrapped + stored in
__vault_meta__:__dek__). Key rotation now means re-wrapping one DEK record, not re-encrypting every credential.Why Argon2id for all master secrets?
Previously there were two code paths — passphrase (KDF) vs raw key (direct). Maintaining two paths is error-prone and confusing. Argon2id on a high-entropy random key is redundant but safe — the cost is a ~100ms KDF on startup, which is acceptable. A single path eliminates the distinction entirely.
Why store the wrapped DEK in the KV store?
The DEK record lives at
__vault_meta__:__dek__in the same KV backend as credentials. This means the vault works with any KV backend (local DiskStore, remote DynamoDB, Redis) without a sidecar file — important for serverless and distributed deployments. The wrapped DEK is protected by AES-256-GCM with the KEK; it is useless without the master secret.Why AES-256-GCM over Fernet?
AES-256-GCM is an AEAD primitive (authenticated encryption with additional data) with a 256-bit key. It is the current standard for symmetric encryption and is directly available in
cryptography.hazmat. Fernet uses AES-128-CBC + HMAC-SHA256 (two primitives, 128-bit key) and is a higher-level convenience wrapper not intended for custom key hierarchies.Changes
AUTHSOME_MASTER_KEYresolution (env → file → keyring → auto-generate); passphrase and raw key both go through Argon2id — no separate code paths__vault_meta__:__dek__BaseEncryptionWrappersubclass using AES-256-GCM per-value encryption; replacesFernetEncryptionWrapperAsyncKeyValue;close()removed (caller manages store lifecycle)ServerConfigandauth.models.__all__crypto_source/crypto_modefrom Vault propertiesSimpleStore(in-memory);DiskStoreremoved from test suiteBreaking changes
Existing Fernet-encrypted vaults cannot be read after this change. Users will need to re-import credentials (
authsome loginagain for each provider).Test plan
uv run pytest— all 321 tests passuv run ruff check src/ tests/— cleanuv run ty check src/— cleanauthsome init && authsome login githubon a fresh~/.authsome__vault_meta__:__dek__record, same plaintext retrieved)GET /healthreturns"encryption_source": "aes-256-gcm"and"encryption_backend": "Argon2id"🤖 Generated with Claude Code