v0.77.0: fork-4 — escrow the offsite restic repo password under R

IdentityBundle gains ResticRepoPassword (rides existing age-under-R
WrapIdentityBundle; custody spike febdc56 proved a recovered value opens the
real repo). POST /escrow/stage-secret (withGuest, scopedFromBody) transiently
stages the controller-pushed password (0600, atomic, NEVER logged), which the
escrow-create ceremony auto-injects then wipes. Adds AttachResticPassword +
StagedResticPasswordPath + WipeStagedResticPassword; EscrowStagePath injectable
for tests. Tests: bundle carries pw byte-exact + not-in-blob + wrong-R fails
closed; stage 0600 + non-secret ack + cross-guest 403 + value-not-in-log.
Additive; PBS-K escrow untouched. NOT yet live-validated (supervised ceremony).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-09 14:56:09 +02:00
parent 7f07393623
commit 0c22b9bbf3
8 changed files with 320 additions and 1 deletions
+25
View File
@@ -1,3 +1,28 @@
## v0.77.0 — fork-4: escrow the offsite restic repo password under R (2026-07-09)
Makes the restic-offsite repo password recoverable at DR by riding the existing customer-recovery-code (R)
zero-knowledge escrow (age-under-R, alongside the IdentityBundle), validated by
`felhom.eu/documentation/audits/SPIKE-restic-password-custody-2026-07-09.md`. Additive; the PBS-K escrow
path is untouched.
- `internal/escrow/identity.go`: `IdentityBundle` gains `ResticRepoPassword` (`restic_repo_password,omitempty`)
— rides the existing `WrapIdentityBundle`/`UnwrapIdentityBundle` age-under-R path (self-verified by
`escrow.Create`). Added `AttachResticPassword` (mirrors `AttachWGKey`), `StagedResticPasswordPath`, and
`WipeStagedResticPassword`. Pre-fork-4 blobs lack the field and CANNOT be retro-fitted (R never retained)
— the controller's atomicity gate ensures no offsite ciphertext exists until the key is escrowed.
- `internal/localapi`: `POST /escrow/stage-secret` (`withGuest`, `scopedFromBody`) transiently stages the
controller-pushed restic password (0600, atomic tmp+rename, **never logged** — field name only, value
never echoed), overwritten on re-push. Stage path injectable via `Options.EscrowStagePath` (default the
canonical `StagedResticPasswordPath`) for testability.
- `cmd/felhom-agent/main.go` (`runSelftestEscrowCreate`): the escrow-create ceremony auto-injects the staged
password into the `IdentityBundle` (mirrors the WG-key auto-inject) and **wipes** the staging file after a
successful create. The ceremony stays operator-invoked (`--selftest=escrow-create`).
- Tests: `IdentityBundle` round-trip carries `ResticRepoPassword` byte-exact + not-in-blob + wrong-R fails
closed; `AttachResticPassword` (missing/present/empty); stage endpoint stages 0600 + non-secret ack +
cross-guest 403 + **value-not-in-log**.
- NOT yet live-validated — the supervised escrow ceremony (enable→stage→escrow-create→confirm→gated run) is
the operator-run follow-up.
## v0.76.0 — restore-test full-fidelity verification (GL-5b / go-live G12) (2026-07-08)
Closes GL-5 finding #2's mirror image: the restore-test's live-source-config bind-override path