Data Encryption at Rest¶
TL;DR: Spector encrypts all stored data using a four-tier architecture — AES-256-GCM for text and WAL, HMAC blind indexing for tags, and infrastructure-level encryption for vectors. Every user gets physically separate files with independent encryption keys, providing isolation that goes far beyond traditional database multi-tenancy.
Overview¶
Spector stores sensitive cognitive memory data — conversations, documents, knowledge graphs, and metadata — across multiple on-disk file types. Each file type has different performance sensitivity, so Spector uses a tiered encryption strategy that matches the right encryption method to the right access pattern.
| Tier | Data | Encryption Method | Where It Happens | Performance Impact |
|---|---|---|---|---|
| Tier 0 | WAL payloads | AES-256-GCM (per-tenant key) | Application layer | ~1µs per event |
| Tier 1 | Raw text (text.dat) | AES-256-GCM (per-tenant key) | Application layer | Zero on search |
| Tier 1b | Entity graph name index | AES-256-GCM (per-tenant key) | Application layer | On save/load only |
| Tier 1c | Salience profiles | AES-256-GCM (per-tenant key) | Application layer | On save/load only |
| Tier 2 | Synaptic tags | HMAC-SHA256 blind indexing | Application layer | Zero (same Bloom comparison) |
| Tier 3 | Vector embeddings (.mem) | LUKS / BitLocker / EBS | OS / Infrastructure | <1% with AES-NI |
Why Not Encrypt Everything at the Application Level?¶
The Spector engine achieves microsecond-latency vector search via a zero-copy architecture:
.memfiles are memory-mapped viaFileChannel.map()intoMemorySegment- The OS page cache handles hot/cold data paging
- SIMD scoring reads directly from these segments via the Panama Vector API
If vectors were encrypted at the application level, every single vector would need to be decrypted before the SIMD dot product could run:
100K memories × 768 dimensions = 300MB of vectors
AES-NI throughput: ~10 GB/s
Decryption overhead: ~30ms per query (added to a 20µs search)
→ Performance degradation: 1,500×
This is why vectors must be encrypted at the infrastructure layer (LUKS, BitLocker, EBS) where decryption happens transparently at the page-cache level via AES-NI hardware acceleration.
Complete User Data Isolation¶
The Problem with Traditional Multi-Tenancy¶
Most systems use row-level security — all tenants share the same database tables, separated by a tenant_id column:
-- Traditional: one shared table, logical isolation
SELECT * FROM memories WHERE tenant_id = 'hospital-a' AND user_id = 'dr-smith';
-- A single SQL injection or ORM bug exposes EVERY tenant's data
This creates fragile isolation:
- A
WHEREclause bug leaks all data - Database administrators see everything in plaintext
- Backups contain all tenants' data in one file
- One encryption key covers the entire database
How Spector Is Different¶
Spector uses physical, filesystem-level isolation where each tenant and each user gets completely separate data files:
/data/namespaces/
├── tenant-hospital-a/
│ ├── dr-smith/ ← Physically separate directory
│ │ ├── semantic.mem ← Encrypted with Dr. Smith's key
│ │ ├── text.dat ← Encrypted with Dr. Smith's key
│ │ ├── wal/wal-000001.bin ← Encrypted with Dr. Smith's key
│ │ ├── index.midx
│ │ └── hebbian.graph
│ ├── dr-jones/ ← Completely separate files
│ │ ├── semantic.mem ← Cannot read Dr. Smith's data
│ │ ├── text.dat ← Different encryption key
│ │ └── ...
│ └── shared-knowledge/ ← Team-wide knowledge base
│ └── ...
├── tenant-hospital-b/ ← Separate directory tree entirely
│ └── ...
Four Levels of Isolation¶
graph TB
subgraph "Level 1: Filesystem Isolation"
FS["Separate directories per user<br/>No shared files, no shared DB tables"]
end
subgraph "Level 2: Memory Isolation"
MEM["Separate mmap MemorySegment per user<br/>No shared buffer pool<br/>Java Arena.ofShared prevents cross-access"]
end
subgraph "Level 3: Cryptographic Isolation"
CRYPTO["Per-user Data Encryption Key (DEK)<br/>Compromising one key → only one user exposed"]
end
subgraph "Level 4: Bring Your Own Key (BYOK)"
BYOK["User provides own key via HTTP header<br/>Server operator cannot decrypt BYOK data<br/>Key exists only in-memory for request duration"]
end
FS --> MEM --> CRYPTO --> BYOK Comparison with Traditional Systems¶
| Isolation Aspect | Traditional DB (Row-Level) | Spector (File-Level) |
|---|---|---|
| Data separation | Logical (WHERE tenant_id = ?) | Physical (separate files on disk) |
| Key granularity | One key per database | One key per user/namespace |
| Admin visibility | DBA sees all plaintext data | Encrypted at rest, keys per user |
| Backup scope | Entire database | Per-user file set |
| Cross-tenant leak risk | SQL injection, ORM bugs | None — separate file handles |
| BYOK support | Typically DB-wide only | Per user/namespace |
| Selective deletion | DELETE (data remains in WAL/backups) | File deletion + secure erasure |
| Blast radius of key compromise | Entire database | Single user's data |
Encryption Architecture¶
Key Hierarchy¶
graph TD
KEK["Root KEK\n(KMS-managed, never leaves secure boundary)"]
KEK --> DEK_A["Tenant 'hospital-a' DEK\n(AES-256, wrapped by KEK)"]
KEK --> DEK_B["Tenant 'hospital-b' DEK\n(AES-256, wrapped by KEK)"]
KEK --> DEK_SYS["System DEK\n(for global data)"]
DEK_A --> TEXT_A["Encrypts text.dat"]
DEK_A --> WAL_A["Encrypts WAL payloads"]
DEK_A --> HMAC_A["HMAC key for blind tags"]
DEK_A --> ENTITY_A["Encrypts EntityGraph name index"]
DEK_A --> SAL_A["Encrypts salience profiles"]
BYOK["BYOK Override\n(user-provided key via header)"]
BYOK -.->|replaces DEK for request| TEXT_A
style KEK fill:#1a73e8,color:white
style BYOK fill:#e8710a,color:white Envelope encryption separates key management from data encryption:
- DEK (Data Encryption Key) — AES-256, per-tenant, encrypts the actual data
- KEK (Key Encryption Key) — wraps/unwraps DEKs, managed by KMS (Vault, AWS KMS, Azure Key Vault, or local file)
This enables key rotation without re-encrypting data: rotate the KEK, re-wrap the DEK, and the data is untouched.
Wire Format¶
All AES-256-GCM encrypted data uses this contiguous byte format:
- IV: 96-bit random nonce (NIST SP 800-38D recommended), fresh per encryption
- Ciphertext: AES-256-GCM encrypted payload
- Tag: 128-bit GCM authentication tag — tamper detection
Tier 0: WAL Payload Encryption¶
The Write-Ahead Log records REMEMBER events with full payloads. Without encryption, a WAL file leak is a complete data breach.
Ingestion path:
encryptedPayload = AES-GCM.encrypt(tenantDEK, quantizedVectorBytes)
WAL.append(REMEMBER, memoryId, encryptedPayload, timestamp)
Replay path:
plainPayload = AES-GCM.decrypt(tenantDEK, encryptedPayload)
→ feed plaintext vector into segment
Tier 1: Text Envelope Encryption¶
text.dat stores plaintext UTF-8 content — conversations, documents, PII. The binary format naturally supports opaque byte payloads:
text.dat Binary Format (V2):
Header: [4B magic "TXTD"] [4B version: 2] [4B count] [4B reserved]
Entries: [1B tier] [4B id_len] [N id_bytes] [4B text_len] [N text_bytes]
↑
Now encrypted with AES-256-GCM
Zero Search Impact
Text is only accessed after the Top-K vector search completes (the cold path). Decrypting 10 results takes <10µs via AES-NI. The search itself (scanning vectors) has zero cryptographic overhead.
Tier 2: HMAC Blind Indexing¶
Synaptic tags are stored as 64-bit Bloom filters in each memory's header. The standard encoder uses non-keyed MurmurHash, which is vulnerable to dictionary attacks:
The output is still a 64-bit long. The SIMD scan loop is completely unchanged — it compares (record.tags & query.tags) == query.tags regardless of how the bits were set.
Tier 3: Volume-Level Encryption¶
Vectors in .mem files are encrypted at the OS/infrastructure layer:
Performance with AES-NI
Modern CPUs decrypt at ~10 GB/s via AES-NI hardware instructions. When the OS page cache loads a page from an encrypted NVMe drive, decryption happens at the hardware level. Once in the page cache, MemorySegment reads plaintext — SIMD loops have zero overhead.
Bring Your Own Key (BYOK)¶
Users can supply their own encryption key via the X-Spector-Encryption-Key HTTP header. This provides the strongest possible isolation — the server operator cannot decrypt BYOK-protected data.
Key Responsibility
BYOK keys are never stored by Spector — they exist only in-memory for the duration of the request. If you lose the key, the data is unrecoverable. This is by design.
Data Flow¶
Ingestion (Writing Encrypted Data)¶
sequenceDiagram
participant Client
participant Enterprise as Control Plane
participant Core as Core Engine
participant Disk
Client->>Enterprise: POST /api/v1/memory/remember<br/>{text, tags}
Note over Enterprise: 1. Resolve tenant from JWT
Note over Enterprise: 2. Load DEK from tenant.json via KMS
Note over Enterprise: 3. Check BYOK header → override DEK?
Enterprise->>Core: remember(text, encryptor, tags)
Note over Core: 4. Embed text → vector
Note over Core: 5. encryptor.encrypt(textBytes)
Note over Core: 6. Write encrypted text to text.dat
Note over Core: 7. Encode tags via KeyedSynapticTagEncoder
Note over Core: 8. Store vector (plaintext) in .mem
Note over Core: 9. Encrypt WAL payload, append to WAL
Core->>Disk: Write encrypted text.dat
Core->>Disk: Write plaintext .mem (disk-encrypted)
Core->>Disk: Write encrypted WAL
Core-->>Enterprise: memoryId
Enterprise-->>Client: 200 OK Recall (Reading Encrypted Data)¶
sequenceDiagram
participant Client
participant Enterprise as Control Plane
participant Core as Core Engine
Client->>Enterprise: POST /api/v1/memory/recall<br/>{query}
Note over Enterprise: Resolve tenant, load DEK
Enterprise->>Core: recall(query, options, encryptor)
Note over Core: 1. Embed query → vector
Note over Core: 2. SIMD scan .mem files (NO crypto)
Note over Core: 3. Top-K results identified (NO crypto)
Note over Core: 4. Read encrypted text for K results
Note over Core: 5. encryptor.decrypt() × K results
Core-->>Enterprise: List of CognitiveRecords
Enterprise-->>Client: 200 OK {results} Key Insight
Steps 2-3 — the expensive SIMD vector search — involve zero cryptographic operations. Decryption only happens in step 5, limited to the K results (typically 5-20 records, each <1µs).
KMS Provider Configuration¶
Spector supports pluggable Key Management Service providers:
Not for production — key is on the same disk as data
Key Rotation¶
KEK Rotation (No Data Re-encryption)¶
Envelope encryption enables KEK rotation without touching any data:
- Rotate KEK in KMS → generates a new key version
- Re-wrap each tenant's DEK:
KMS.rewrap(wrappedDek, keyName)— re-encrypts the DEK with the new KEK without exposing the plaintext DEK - No data re-encryption needed — the DEK itself doesn't change, only its wrapping
DEK Rotation (Requires Data Re-encryption)¶
When a tenant's DEK must be rotated (e.g., after a suspected compromise):
- Generate a new DEK
- Run the offline migration tool on the tenant's partition
- Update
tenant.jsonwith the new wrapped DEK and incrementeddekVersion
Per-Tenant DEK Rotation API¶
Enterprise deployments expose a REST endpoint for programmatic key management:
| Endpoint | Method | Description |
|---|---|---|
/api/v1/enterprise/encryption/tenant/{tenantId}/keys | GET | List active DEKs for a tenant |
/api/v1/enterprise/encryption/tenant/{tenantId}/keys/rotate | POST | Generate new DEK and schedule re-encryption |
/api/v1/enterprise/encryption/tenant/{tenantId}/keys/status | GET | Check re-encryption progress |
Threat Model¶
| Threat | Mitigation | Status |
|---|---|---|
| Disk theft / file exfiltration | Volume encryption + application-level text/WAL encryption | |
| Database dump | H2 AES encryption enabled by default | |
| Tag brute-forcing | HMAC-SHA256 blind indexing with per-tenant keys | |
| WAL replay attack | WAL payloads encrypted with tenant DEK | |
| Cross-tenant data leak | Filesystem isolation + per-tenant DEKs | |
| Cross-user data leak | Per-namespace DEKs + separate mmap segments | |
| Operator accessing user data | BYOK — user's key never stored | |
| Entity graph name leakage | AES-256-GCM encrypted name index | |
| Salience profile leakage | AES-256-GCM encrypted profile storage | |
| Vector embedding inversion | Infrastructure encryption + quantization noise | Defense-in-depth |
| Swap file leakage | Encrypted swap + memory pinning (mlock) | Operator config |
| Key compromise blast radius | Per-tenant/per-user DEKs limit exposure |
Startup Encryption Audit¶
On every boot, Spector runs an encryption compliance audit:
╔══════════════════════════════════════════════════╗
║ Spector Enterprise — Encryption Audit ║
╠══════════════════════════════════════════════════╣
[P0] ✅ H2 Database Encryption — ENABLED
[P0] ✅ Data-at-Rest Encryption — ENABLED (AES-256-GCM)
[P1] ✅ KMS Provider — Connected: vault
[P1] ✅ Volume Disk Encryption — ENCRYPTED (LUKS detected)
[P2] ✅ JWT Secret — Custom secret configured
╠══════════════════════════════════════════════════╣
║ Result: 5/5 checks passed
╚══════════════════════════════════════════════════╝
In strict security mode (SPECTOR_STRICT_SECURITY=true), P0 failures block startup.
Compliance Mapping¶
| Requirement | SOC 2 | HIPAA | GDPR | Spector Coverage |
|---|---|---|---|---|
| Encryption at rest (data) | Required | Mandatory | Art. 32 | AES-256-GCM text + WAL |
| Encryption at rest (DB) | Required | Mandatory | Art. 32 | H2 AES (default on) |
| Encryption at rest (vectors) | Required | Mandatory | Art. 32 | Volume encryption |
| Key management (KMS/HSM) | Required | Required | Recommended | Vault / AWS / Azure |
| Key rotation | Required | Required | Recommended | KEK + DEK rotation |
| Audit logging | Required | Required | Required | Startup audit + JDBC logger |
| Access control (RBAC) | Required | Required | Required | JWT + API keys + scopes |
| Data isolation | Required | Required | Required | File-level per-user |
| Right to forget | — | — | Art. 17 | Secure erasure on forget() |
| BYOK | Recommended | Recommended | Art. 32 | Per-request user key |
Environment Variables¶
| Variable | Default | Description |
|---|---|---|
SPECTOR_DATA_ENCRYPTION | true | Enable/disable data-at-rest encryption |
SPECTOR_DB_ENCRYPT | true | Enable H2 AES database encryption |
SPECTOR_DB_ENCRYPT_KEY | (auto) | H2 encryption key (auto-generated if not set) |
SPECTOR_KMS_PROVIDER | local | KMS provider: local, vault, aws, azure |
SPECTOR_STRICT_SECURITY | false | Block startup if encryption checks fail |
Next Steps¶
- Dopamine — Surprise Detection — how importance is computed
- Salience & Importance — personalized importance via salience profiles
- Synapse — Tags & Scoring — the 64-byte header where encrypted tags live
- WAL Design — write-ahead log internals