SapixDBSapixDB/Docs
Home

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.

Key derivation diagram
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)
Blast radius is one agentBecause each agent key uses the agent ID as the HKDF salt, an attacker who recovers one derived key learns nothing about keys for other agents. The master seed is the only secret that protects everything — guard it accordingly.

What is and is not encrypted

DataEncrypted?Reason
Record payload (your JSON data)YesStored as nonce ‖ ciphertext ‖ auth_tag in each segment file.
Blob storageYesEncrypted separately under a blob-specific derived key.
WAL entries (wal.bin)No — plaintextCrash recovery must replay WAL before keys are available. WAL is cleared after a clean seal.
content_hash, parent_hashNo — plaintextChain integrity verification reads hashes without decrypting payloads.
timestamp_hlc, flagsNo — plaintextNeeded for ordering, chain verification, and index operations.
WAL exposure windowWAL entries contain plaintext payloads. The WAL is written on every record write and cleared when a segment is sealed (~4 MB). On a busy agent this window is short, but ensure your WAL directory is on an encrypted volume if you require full at-rest protection.

Generating a master seed

The master seed must be exactly 64 hexadecimal characters (32 bytes of entropy). Use a cryptographically secure random source:

Generate seed (Python)
python3 -c "import secrets; print(secrets.token_hex(32))"
Generate seed (OpenSSL)
openssl rand -hex 32

Set the result as an environment variable before starting the agent:

Environment
SAPIX_MASTER_SEED=a3f1c92e4b78d0e56f2a1c8b9d3e7f04...  # 64 hex chars
Back up your master seed offlineIf 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:

Log output
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.

Flags reference
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:

  1. Stop the agent.
  2. Run the offline re-encryption tool against SAPIX_STRAND_DIR with the old and new seeds.
  3. Replace SAPIX_MASTER_SEED in the environment with the new seed.
  4. Restart the agent.
Re-encryption is a full-table operationOn large deployments, re-encrypting all segment files is time-consuming. Schedule it during a maintenance window and verify chain integrity after completion.

Summary

PropertyValue
CipherAES-256-GCM
Nonce size12 bytes, random per record
Auth tag size16 bytes
Key derivationHKDF-SHA256, salt = agent_id, info = "strand-payload-encryption-v1"
Overhead per record28 bytes (12 nonce + 16 tag)
Encrypted flag0x20 in flags byte
Config env varSAPIX_MASTER_SEED (64 hex chars)