# REPORT — Slice 7 close-out: PBS recovery-code escrow creation (v0.9.0) (2026-06-10) > Overwrite-latest report (most recent significant work only). Cumulative history lives in > [CHANGELOG.md](CHANGELOG.md). Implements the agent half of `TASK — Slice 7 close-out: PBS > recovery-code escrow creation`. The hub opaque-storage half is in `felhom.eu` (hub v0.8.0). ## Outcome The first code that touches the PBS client encryption key `K` and introduces the customer recovery code `R`. Default posture is **zero-knowledge**: Felhom holds an opaque `R`-wrapped blob it cannot open; the customer holds `R`. The wrap→recover→restore round-trip was proven on a fenced throwaway before any code (`felhom.eu/documentation/tests/slice7-escrow-spike-findings.md`) — the `R`-recovered key restored a real encrypted snapshot. ## Secret discipline (the overriding rule — held throughout) - `R`: `crypto/rand`, ≥128 bits, surfaced **exactly once** (selftest stdout), **never** logged, persisted, committed, or in this report. The wrap pty's echoed passphrase is **discarded** (F-A2). - `K`: read by location, **never modified** (Wrap operates on a copy; the live key file is byte-unchanged — asserted live by sha256), never logged. - The blob is opaque ciphertext; the hub stores bytes and never decrypts. ## What landed (`internal/escrow`) - **`Create`** → generate `R` (10 EFF-wordlist words ≈ 129 bits) → **Wrap** `K` under `R` via the PBS-native `proxmox-backup-client key change-passphrase --kdf scrypt`, driven over a **stdlib pty** (`x/sys/unix`; the command is TTY-only, spike F-A1) with **output discarded** (F-A2) → **self-verify** the blob unwraps back to `K` (fingerprint match) before shipping → return `R` SEPARATELY (so it can't be logged via the result) + the opaque blob. - **Opt-in outputs:** (b) `R`-wrapped offline copy (two-factor, no extra trust); (a) raw paperkey (single-factor, unrevocable — loud caveat). - **`--selftest=escrow-create`** (`-storage`/`-paperkey`/`-offline`/`-upload`): surfaces `R` once, prints the opaque blob's size/fingerprint/posture, and with `-upload` PUTs it to the hub (`/api/v1/hosts/{host_id}/escrow`, per-host key). - Config: `escrow{posture, pbs_storage_id}` (zero_knowledge default); `PBSEncKeyPath` (the `.enc`). - Runtime dependency: the `proxmox-backup-client` CLI. ## Tests `go test ./...` green; `-race` green on the build server (pure-Go parts). `R` entropy ≥128 / 10-word format / 200-draw uniqueness; the agent→hub wire-contract key-set (mirrors the hub). The integration round-trip (wrap→unwrap fingerprint match, **wrong-`R` fails**, **live `K` byte-unchanged**, blob ≠ plaintext) is guarded to linux+`proxmox-backup-client` and exercised by the live selftest. ## Live validation (demo-felhom) `--selftest=escrow-create -storage felhom-pbs`: `R` (10 words) surfaced **once** to stdout; blob **383 B opaque** (key fingerprint `01:36:e9…`, posture `zero_knowledge`, ~129-bit `R`); **self-verify** confirmed recoverability; the live `K` (`felhom-pbs.enc`) **sha256 byte-unchanged**; the exact `R` was **absent** from stderr and the agent journal. *(Upload to the hub validated after hub v0.8.0 deploy — see felhom.eu REPORT.)* ## Deferred (stated, not built) Escrow **consumption / restore-mode serving** → slice 10 (host-loss DR; the spike pre-validated it). The **Felhom-holds-a-key** posture (the one dangerous topology cell) is documented (doc 03 §8a) and implemented only on an explicit customer opt-in. No secrets committed.