> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sovseal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 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.

<Info>
  **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.
</Info>

***

## Master key custody (OS keychain)

The master key is held by the platform credential store via [`@napi-rs/keyring`](https://www.npmjs.com/package/@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:

```typescript theme={null}
const master = crypto.getRandomValues(new Uint8Array(32)); // 256-bit master
await keychain.setPassword("sovseal", "master", base64(master));
```

<Warning>
  **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.
</Warning>

***

## 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:

```text theme={null}
k_rest = HKDF-SHA256(master, info = "sovseal/at-rest/v1")   // local field encryption
k_sync = HKDF-SHA256(master, info = "sovseal/sync/v1")      // replication ciphertext
```

* `k_rest` encrypts memory `text` at rest in the local LanceDB store.
* `k_sync` seals the ciphertext that replicates to the server.

Both are imported as **non-extractable** AES-GCM `CryptoKey`s — they exist only inside the running process and cannot be exported back out.

<Warning>
  **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.
</Warning>

***

## What the local config file holds now

`~/.sovseal/config.json` (`0600`) holds identity and routing only — **no key material**:

```json theme={null}
{
  "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

<Warning>
  **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.
</Warning>

***

## Related

* [AES-256-GCM](/components/encryption/aes-256-gcm) — the cipher used for both at-rest and sync.
* [Zero-Knowledge Guarantees](/platform/core-concepts/zero-knowledge) — what the server can and cannot see.
* [Trust Center](/platform/trust) — the consolidated threat model.
