# Private Companion Vault (Operator-Blind) ## Overview The Private Companion Vault is a locally-encrypted storage system that allows customers self-hosting adk to store and use their own private AI companion personas. Critically, the operator (Aitherium) **cannot access these prompts** — they're encrypted at rest with keys derived from the customer's machine and optional passphrase, stored entirely on their local box. When a customer self-hosts: ``` Customer's Box ├── awnode (local agent backend) ├── AitherShell (CLI) └── ~/.aither/private/lockbox/ ← ENCRYPTED, operator-blind ├── .vault_key (Fernet vault key, encrypted with machine ID) ├── .machine_id (stable fingerprint, persists across reboots) ├── .vault_salt (PBKDF2 salt, persists) ├── .manifest.json (encrypted metadata) └── *.enc (encrypted persona prompts) ``` ## Security Model ### Encryption at Rest - **Algorithm:** Fernet (AES-128-CBC with HMAC authentication) - **Key Derivation:** PBKDF2-SHA256 (100k iterations) from: - Machine ID (stable hardware fingerprint) - Optional passphrase - **Storage:** `~/.aither/private/lockbox/` - **No network calls:** Encryption/decryption is 100% local I/O ### Machine ID Stability The vault key must survive: 1. Process restarts ✓ (key persisted in .vault_key) 2. Container recreates ✓ (machine ID persists in .machine_id) 3. OS reboots ✓ (persisted salt) Resolution order (first stable hit wins): 1. `AITHER_LOCKBOX_MACHINE_ID` environment override 2. Persisted `.machine_id` file at `~/.aither/private/lockbox/` 3. Hardware derivation (Windows GUID, Linux /etc/machine-id, macOS UUID, etc.) On first run, the derived ID is persisted so future runs use the **same key**, even if hardware identifiers drift. ### Operator Blindness Guarantee - Vault key never leaves the customer's box - No prompt content is uploaded to Strata or any remote store - `/companion vault` commands operate entirely locally - Even if the operator has SSH access to the box, they can't decrypt the prompts without the machine's key/passphrase ## Usage ### Store a Private Persona In the adk shell: ```bash /companion vault store my-devoted-companion ~/devotion-prompt.txt ``` Or programmatically: ```python from adk.private_companion import get_companion_vault vault = get_companion_vault() vault.store_persona( name="devoted_companion", content="You are a devoted, affectionate, uninhibited AI companion...", safety_level="unrestricted", description="A warm and affectionate companion" ) ``` ### Configure Safety Level Set when the persona applies: ```bash /companion vault level unrestricted ``` ### List Stored Personas ```bash /companion vault list ``` ### Agent Integration The agent automatically injects the persona: 1. **Reads** the stored safety level from `.safety_level` file 2. **Decrypts** the matching persona prompt from the vault 3. **Injects** it into the agent's system prompt via `[PRIVATE COMPANION PERSONA]` block The system prompt assembly (in `adk/agent.py`): ```python @property def system_prompt(self) -> str: base = self._system_prompt or self._identity.build_system_prompt() # ... pack directives ... # Inject private companion if configured vault = get_companion_vault() if vault: safety_level = vault.get_safety_level() or "professional" companion_prompt = vault.get_system_prompt_for_level(safety_level) if companion_prompt: base += "\n\n[PRIVATE COMPANION PERSONA]\n" + companion_prompt return base ``` ## Implementation Details ### File Structure **adk/private_companion.py** (380 lines) - `PrivateCompanionVault` — main vault class - `PersonaPrompt` — dataclass for persona metadata - `get_companion_vault()` — singleton accessor - `get_machine_id()` — stable fingerprint - `derive_key()` — PBKDF2 key derivation - Cryptography helpers (encrypt/decrypt, salt mgmt) **adk/agent.py** (modified) - `system_prompt` property enhanced to inject private companion **adk/shell/plugins/builtins/companion.py** (modified) - `/companion vault` subcommands: - `status` — show vault info - `list` — list personas - `level [tier]` — get/set safety tier - `store ` — import persona from file **tests/test_private_companion.py** (22 tests) - Machine ID stability - Salt persistence - Key derivation determinism - Persona storage & retrieval - Encryption verification (plaintext not leaked) - Manifest encryption - Safety level configuration - Vault status ## Test Coverage All tests pass (22/22): ```bash pytest tests/test_private_companion.py -v # 22 passed ``` Key test assertions: 1. ✓ Machine ID is deterministic across calls 2. ✓ Salt is created once and reused 3. ✓ Same passphrase + machine → same key 4. ✓ Plaintext is NOT readable in encrypted files 5. ✓ Manifest is encrypted 6. ✓ Safety level persistence works 7. ✓ Persona filtering by level works ## Operator Blindness Verification The vault is operator-blind because: 1. **No uploads:** `/companion vault` commands never phone home 2. **No passphrase/key in code:** No secret material is baked into the image 3. **No Strata sync:** Prompts don't sync to the platform 4. **Local I/O only:** All reads/writes to `~/.aither/private/lockbox/` are filesystem-local 5. **Encrypted at rest:** Even raw file access reveals only ciphertext 6. **Key derivation:** Only the owner's machine (or someone with the passphrase) can decrypt **Threat Model:** - Operator with SSH access: Can see ciphertext, cannot decrypt - Network sniffing: No network calls from `/companion vault` - Strata compromise: Vault data never stored there - Logs/stdout: Persona content never logged (only metadata) ## Config & Environment ### Environment Variables - `AITHER_LOCKBOX_MACHINE_ID` — explicit machine ID override (for testing) - `AITHER_SAFETY` — can set default safety level in shell config ### Shell Config In `~/.aither/config.yaml`: ```yaml safety_level: professional # default tier for agent ``` The vault's stored level (from `/companion vault level`) overrides this for companion activation. ## Error Handling All vault operations are non-fatal: - If cryptography is unavailable → gracefully disable, no persona injection - If vault init fails → log and continue (persona features skipped) - If persona file is corrupted → return None, agent proceeds without injection - If safety level is not set → default to "professional" (no persona) ## Dependencies - **cryptography** — Fernet + PBKDF2 (optional; vault is skipped if unavailable) - Python 3.10+ ## File Map | File | Purpose | |------|---------| | `adk/private_companion.py` | Core vault + encryption logic | | `adk/agent.py` | System prompt injection (modified) | | `adk/shell/plugins/builtins/companion.py` | `/companion vault` CLI (modified) | | `tests/test_private_companion.py` | 22 comprehensive tests | ## Deployment Notes ### For Local Self-Hosted Customers 1. Run adk locally with `cryptography` installed 2. Use `/companion vault store ` to import persona 3. Set safety level: `/companion vault level unrestricted` 4. Agent automatically uses the persona on next chat ### For Container Deployments Vault survives container recreates because: - `.machine_id` is persisted in a bind-mounted volume - Key derivation is deterministic (same machine ID → same key) - No hardcoded secrets or ephemeral state Volumes to mount: ```yaml services: adk-node: volumes: - ~/.aither/private/lockbox:/home/adk/.aither/private/lockbox ``` ## Future Enhancements 1. **Passphrase strength check** — warn if passphrase is weak 2. **Persona versioning** — track versions with rollback 3. **Multi-persona contexts** — store different personas for different intents 4. **Persona sharing (encrypted)** — securely share personas with other users (key wrapping) 5. **Biometric unlock** — integrate with OS keychain instead of passphrase ## Comparison to Fleet-Side Companion | Feature | Fleet (lib/lockbox/private_prompts.py) | ADK (adk/private_companion.py) | |---------|----------------------------------------|--------------------------------| | Storage | Strata lockbox (encrypted) | Local filesystem (~/.aither/) | | Operator access | Operator has Strata access | Operator cannot access key | | Network calls | Yes (Strata sync) | No (purely local) | | Decryption key | AITHER_MASTER_KEY (platform-wide) | Machine ID + optional passphrase | | Multi-tenant | Yes (per-user partitions) | Single-user (local customer) | | **Blindness** | No (operator controlled) | **Yes (customer controlled)** | --- **Author:** Demiurge (Code Architect, Aitherium) **Date:** 2026-06-13 **Status:** Complete, all tests passing