Manual · Storage
Strand Encryption
SapixDB encrypts strand record payloads at rest using AES-256-GCM. Each agent gets a unique encryption key derived from a single master seed, so compromising one agent's key does not expose data in any other agent.
Key derivation model
A single environment variable — SAPIX_MASTER_SEED— is the root of all encryption. Per-agent keys are derived from it using HKDF-SHA256 so that each agent's key is cryptographically independent.
SAPIX_MASTER_SEED (64 hex chars = 32 bytes of entropy)
│
▼ HKDF-SHA256(
│ salt = agent_id,
│ info = "strand-payload-encryption-v1"
│ )
│
▼ Per-agent AES-256 key (unique per agent_id)
│
▼ AES-256-GCM encrypt(
│ plaintext = payload_msgpack,
│ nonce = random 12 bytes (generated per record)
│ )
│
▼ Stored on disk:
nonce (12 bytes) ‖ ciphertext (N bytes) ‖ auth_tag (16 bytes)What is and is not encrypted
| Data | Encrypted? | Reason |
|---|---|---|
| Record payload (your JSON data) | Yes | Stored as nonce ‖ ciphertext ‖ auth_tag in each segment file. |
| Blob storage | Yes | Encrypted separately under a blob-specific derived key. |
WAL entries (wal.bin) | No — plaintext | Crash recovery must replay WAL before keys are available. WAL is cleared after a clean seal. |
content_hash, parent_hash | No — plaintext | Chain integrity verification reads hashes without decrypting payloads. |
timestamp_hlc, flags | No — plaintext | Needed for ordering, chain verification, and index operations. |
Generating a master seed
The master seed must be exactly 64 hexadecimal characters (32 bytes of entropy). Use a cryptographically secure random source:
python3 -c "import secrets; print(secrets.token_hex(32))"
openssl rand -hex 32
Set the result as an environment variable before starting the agent:
SAPIX_MASTER_SEED=a3f1c92e4b78d0e56f2a1c8b9d3e7f04... # 64 hex chars
SAPIX_MASTER_SEED is lost, every encrypted segment file becomes permanently unreadable. Store the seed in a secrets manager (e.g. AWS Secrets Manager, HashiCorp Vault, Railway secret store) and maintain an offline backup in a secure location.Startup behaviour when the key is missing
If SAPIX_MASTER_SEED is absent from the environment at startup, the agent refuses to initialize and logs a fatal error:
ERROR sapix_agent: SAPIX_MASTER_SEED not set — cannot initialize encryption. Aborting.
The process exits immediately. No data is written and no HTTP listener is opened. This prevents accidentally starting an unencrypted instance against an encrypted data directory.
The encrypted flag in RecordView
Every stored record carries a flags byte. When a payload has been encrypted, the engine sets bit 0x20 automatically. You never set this flag manually — it is applied at write time and checked at read time.
0x00 — no flags (plaintext payload) 0x01 — deleted tombstone 0x20 — encrypted payload (set by the engine) 0x21 — encrypted + deleted
When reading a record, if flags & 0x20 !== 0, the agent derives the per-agent key and decrypts the payload before returning it. The HTTP response always contains the plaintext JSON — encryption is transparent to API consumers.
Key rotation
Key rotation requires re-encrypting every segment file for every affected agent. There is no online rotation path — the process is:
- Stop the agent.
- Run the offline re-encryption tool against
SAPIX_STRAND_DIRwith the old and new seeds. - Replace
SAPIX_MASTER_SEEDin the environment with the new seed. - Restart the agent.
Summary
| Property | Value |
|---|---|
| Cipher | AES-256-GCM |
| Nonce size | 12 bytes, random per record |
| Auth tag size | 16 bytes |
| Key derivation | HKDF-SHA256, salt = agent_id, info = "strand-payload-encryption-v1" |
| Overhead per record | 28 bytes (12 nonce + 16 tag) |
| Encrypted flag | 0x20 in flags byte |
| Config env var | SAPIX_MASTER_SEED (64 hex chars) |