Key Management & Custody
How the master key is generated, held in the OS keychain, and split into purpose-bound subkeys via HKDF.
sovseal uses no external KMS or cloud HSM. Custody is local-first: a single master key lives in the operating-system keychain, and every working key is derived from it on demand. The server never receives any of them.
Changed in 0.3.5
Earlier builds stored the raw 256-bit key as encryption_key_b64 inside ~/.sovseal/config.json (mode 0600). As of 0.3.5, custody moved to the OS keychain; on first run after upgrade the old field is migrated into the keychain and tombstoned in the file. If you have automation that reads encryption_key_b64, update it.
Master key custody (OS keychain)
The master key is held by the platform credential store via @napi-rs/keyring:
- macOS — Keychain
- Windows — Credential Manager
- Linux — Secret Service / libsecret
It is generated once on first run with a CSPRNG and written to the keychain — never to a plaintext file:
const master = crypto.getRandomValues(new Uint8Array(32)); // 256-bit master
await keychain.setPassword("sovseal", "master", base64(master));Opt-in file fallback
On headless machines without a keychain you may set SOVSEAL_KEY_FALLBACK=file, which stores the master at ~/.sovseal/ with 0600 permissions. If the keychain is unavailable and this flag is not set, sovseal fails closed rather than silently downgrading custody.
Subkey derivation (HKDF-SHA256)
Working keys are never used raw. The master is split by purpose using HKDF-SHA256 with domain-separation labels, so a compromise of one context cannot be replayed against another:
k_rest = HKDF-SHA256(master, info = "sovseal/at-rest/v1") // local field encryption
k_sync = HKDF-SHA256(master, info = "sovseal/sync/v1") // replication ciphertextk_restencrypts memorytextat rest in the local LanceDB store.k_syncseals the ciphertext that replicates to the server.
Both are imported as non-extractable AES-GCM CryptoKeys — they exist only inside the running process and cannot be exported back out.
Changing a label is a key-version bump
The HKDF info labels are versioned (/v1). Changing a label changes the derived key and renders existing ciphertext unreadable. Treat any label edit as a migration, not a refactor.
What the local config file holds now
~/.sovseal/config.json (0600) holds identity and routing only — no key material:
{
"schema_version": 1,
"project_id": "8435d886-f288-466c-8ee1-eb836e2b6912",
"api_key": "sov_proj_8435d886-f288-466c-8ee1-eb836e2b6912",
"endpoint": "https://<your-project>.supabase.co/functions/v1/v2-agent-state",
"created_at": "2026-06-09T16:41:23.000Z"
}After migration the legacy encryption_key_b64 is replaced with a tombstone marker; the live key is in the keychain.
Loss & recovery
There is no escrow and no reset. If you lose the keychain master key (or, under the file fallback, the fallback key file), every snapshot — local and replicated — becomes permanently undecryptable. Zero-knowledge means the server cannot help. Back up the master key material if continuity matters.
Related
- AES-256-GCM — the cipher used for both at-rest and sync.
- Zero-Knowledge Guarantees — what the server can and cannot see.
- Trust Center — the consolidated threat model.