# REPORT — v0.122.0: customer-claim password gate (closes DRILL-day0-vm F-4/F-5)

**Date:** 2026-07-12 · **Version:** controller v0.122.0 (from v0.121.0) · **MinAgent:** 0.81.0 (UNCHANGED) · **Requires:** hub v0.50.0

## What shipped

The dashboard password is now **customer-owned**, set through a one-time claim code the hub emails
to the registered address. The old "no password → open dashboard" default is gone: a fresh box
(claim-code hash delivered, no password) serves ONLY the claim page — every other route answers the
claim page (`302 → /claim`) or `401` (API). This closes the DRILL-day0-vm F-4 exposure (every Day-0
dashboard was open on the internet, with the full deploy API = RCE-equivalent) and F-5 (the
unauthenticated geo toggle is now behind the gate too).

- **`internal/web/claim.go`** — `claimGateActive()` / `effectiveClaimCode()` (ACK-cached settings
  beats the config bake by generation), the claim page (`GET /claim`), submit (`POST /claim`:
  verify code → set own password via `SetPasswordHash` → mark claimed → consume generation →
  session), and `POST /claim/request-new-code` (forwards to the hub reset-request). Code checks:
  bcrypt match AND generation-not-consumed (single-use) AND ≤ 72 h. Per-source + global limiter (5
  → 15-min lockout) raising the allowlisted `claim_lockout` event. Pre-auth CSRF = HMAC over
  `web.session_secret` (fixes CTRL-007). Min password 12.
- **Gate wiring** (`auth.go`/`csrf.go`/`server.go`/`cmd`): gate atop `RequireAuth`; a SET password
  disables it (auth wins — claimed boxes never regress). `/claim*` + `/static/*` reachable pre-auth.
  Legacy-open (no password, no hash) → red transition banner (`layout.html`). Login "Elfelejtett
  jelszó" link.
- **`internal/report/claim_sync.go`** — caches the ACK `claim {hash, generation}` into settings.json
  idempotently by generation (one-way: newer advances; same/older/nil never rewrites; hub outage
  never clears). Report carries `claimed` (set-only). Hub bakes `web.claim_code_*` for gate-from-
  first-boot.
- **`--print-reset-code`** root escape hatch (one-time local code above cached/baked/consumed gen).

## Tests + red-proofs

- Gate-coverage signature test (`TestClaimGate_EveryRouteGated`): every route → claim page/401, a
  deploy POST reaches no handler. Plus happy-path / reuse-refused / expired / lockout+window-reopen
  (fake clock), legacy-open vs password-set, claim-sync idempotence.
- **Four §10 red-proofs proven** (mutate → FAIL → revert → green): (1) gate skip-line, (2) single-use
  generation — hub `RotateClaimCode` bump AND controller consumed-generation check, (3) reset
  non-DoS (clearing claimed → fail), (4) rate-limiter (both scopes disabled → fail).
- Green gate: `go build ./... && go vet ./...` clean; all touched packages pass. Pre-existing
  env-sensitive `internal/backup` volume-dump failures are unrelated (fail identically on the clean
  baseline — no tar/zstd toolchain on the dev box).

## Live validation (drill guest 9201 on qm 300, controller 0.122.0, pre-floor)

Deployed 0.122.0 → the ACK delivered the claim hash → gate flipped ON within a report cycle. Proven
via the REAL Cloudflare edge (`curl --resolve`): `/` → 302 → "A szerver beállítása" claim page;
`/claim` → 200; `/api/host-metrics` → 401. Controller log: `claim-sync: hub claim code cached
(generation 1)`. Hub log: claim code emailed to the registered address of demo-vm-felhom. The
customer-side claim + reset + the floor raise are the operator's supervised steps.
