hub v0.159.0 (R-1154, after security review): the enroll reuse path re-issues nothing, it warns the operator

The first draft (5121536f, unpushed) re-issued the claim code and the off-site login on a
reuse whose old box was stale or switched off. A security review flagged it: the caller
holds only the household's retrieval passphrase, and both re-issues are operator
session-only actions (05 section 17.5, 09 decision 252, R-1055/R-1059). Now the reuse
path classifies the old box and re-issues nothing: gone -> host_reinstall_over_old_host
(warning, mailed to the operator: delete the old host and re-run, or re-issue by hand);
live -> host_reenroll_while_live (info). The mint path after the operator's delete is
unchanged and still re-issues. The once-per-silence record and the claim engine comment
edit are reverted. 05 section 8, README and CHANGELOG updated; tests red-proved.
This commit is contained in:
2026-10-19 03:29:44 +02:00
parent 5121536f10
commit b505ce4ba0
9 changed files with 265 additions and 268 deletions
@@ -222,24 +222,23 @@ Today: `GET /config/{id}` with an `X-Retrieval-Password` (Hungarian passphrase)
The existing `configgen` deep-merge + Hungarian-passphrase machinery is the base; it grows the
agent-first + key-pinning + restore-mode steps.
**A reinstall over an existing host record re-issues when the old box is gone [DESIGN, 2026-10-19 — R-1154, brief
**A reinstall over an existing host record is made loud, never acted on [DESIGN, 2026-10-19 — R-1154, brief
2026-10-19; hub v0.159.0].** `POST /host-enroll` is the installer's step 4, run on every pass (a `--resume` too), with
the customer's passphrase; an existing host record is REUSED (same id and key — the reinstalled box takes it over).
The clean-slate re-issues (the claim code, the off-site login — both live in the CONTROLLER) used to run only when a
record was minted, so a reinstall that kept the old record skipped both in silence. The reuse path now runs them
when, and only when, the old box is gone: **no controller wait connection open now**, and either the household
switched the box off on purpose (`deliberate_off_at` inside its hold) or **both** the agent's host report and the
controller's report are older than `alerting.stale_threshold` (or absent) — the threshold and comparison
`hostStatus` uses, no second definition. **Once per silence:** not again when it already ran since the host's last
report (an installer `--resume` before the new agent's first report). **Why this rule and not "re-issue on every
reuse":** a live box re-enrolls legitimately (a `--resume` after its agent started, a repair re-run), and re-issuing
there rotates the household's claim code and resets its off-site login under a running box; not re-issuing costs a
reinstall two operator presses, which the skip record now names. The controller report counts because the codes
live there: a box whose agent is broken but whose controller reports is live. **Known residual:** a reinstall done
less than the threshold after the old box's last report is still treated as live — it is recorded
(`host_reenroll_not_reissued`, operator-only, info) instead of silent, and the operator presses the two buttons or
deletes the old host first. The re-issue writes `host_reenroll_reissued` (also the once-per-silence record); both
types are hub-origin only. `hub/internal/api/reenroll.go`, pinned by `hub/internal/api/r1154_reenroll_test.go`.
the customer's retrieval passphrase; an existing host record is REUSED (same id and key — the reinstalled box takes
it over). The clean-slate re-issues (a new claim code mailed to the household, the off-site provider password reset)
run only on the MINT path, i.e. after the operator deleted the old host — **the operator's delete is their gate,
and stays so.** A reinstall that kept the old record used to skip them in silence. Now the reuse path classifies the
old box and re-issues NOTHING: **gone** (no controller wait connection open now, and either `deliberate_off_at`
inside its hold or BOTH the host report and the controller report older than `alerting.stale_threshold` or absent —
the `hostStatus` threshold and comparison) → the operator-only WARNING `host_reinstall_over_old_host`, mailed to the
operator: delete the old host (Hosts page) and run the installer again, or re-issue the claim code and the off-site
login from the customer page; **live** → the operator-only INFO `host_reenroll_while_live`, recorded, never mailed.
**Why not re-issue automatically** (a security review of the first draft, the same day): the passphrase is a
household-held secret typed into the installer, and both re-issues are operator SESSION-ONLY actions — not even an
operator token may run them (§17.5; `09` §3 decision 252, R-1055/R-1059: an off-site re-issue „stops its backup for a
while"). Re-issuing on the reuse path would give those powers to anyone holding the passphrase whenever the box is
switched off or silent. The cost of the loud shape is two operator acts per reinstall that kept its old record. Both
event types are hub-origin only. `hub/internal/api/reenroll.go`, pinned by `hub/internal/api/r1154_reenroll_test.go`.
## 9. DR model