Files
felhom.eu/REPORT-hub-tokens.md
T

6.8 KiB

REPORT — hub access tokens (hub v0.152.0), 2026-10-15

For the operator — plain words

What a token can do. A token lets a script use the hub without your password. You manage tokens on Configuration → Access tokens. You give a name, tick what it may do, and pick 30, 90, 180 or 365 days. The hub shows the token once. It keeps only a scrambled form. Every use is logged with the token's name.

What no token can do. Show a box's emergency root password. Show a household's passphrase or controller key. Delete or reset anything. Change the password. Create or revoke tokens. Reach the box API (/api/v1).

Is CC using its token now? Yes. Hub 0.152.0 is live (your „yes", 2026-10-15). With the token „cc-releases":

  • a floor save with the same values (0.313.0, MinAgent 0.155.0) worked, and the hub database shows it;
  • the vouch was refused: „missing scope vouch:write";
  • the customer page and the emergency-password reveal were refused: „session only". All three boxes reported to the new hub after the restart.

What you do now.

  1. Change the hub password. CC no longer needs it.
  2. Delete the HUB_PW line in ~/.config/credentials on DooPlex. CC does not delete it.
  3. Your token is wider than the plan: it can also change off-site windows, re-issue off-site keys, freeze off-site, and approve OS updates. CC uses only the floor. Re-create it narrower if you want; CC will not widen or narrow it.
  4. Decide R-1055: should „resend claim" need your password? My pick: yes.
  5. Proof-install clean-up (discarding an unclaimed appliance) is password-only now. CC will ask you to press Discard.

Side effect. Each floor save (two today, both with the same value) mailed you „demo-hp's own floor 0.311.0 blocks the global floor". That is the old item: clear demo-hp's own floor.

Was the hub ever open without a password? The code allowed it. The live hub was never open (it asked for a login, and the manifest carries a password hash). Fixed: no password now means every operator page refuses.

What was built

  • Token store api_tokens (SHA-256 only), the guard in RequireAuth, the route table (hub/internal/web/routes.go), the Configuration card, the box API's refusal of tokens, the box API's route table (hub/internal/api/routes.go).
  • Design: documentation/architecture/05-hub-architecture.md §17 (read first, with §16). Also 04 §6.2, 13 §4/§5, 09 §3 decision 251.
  • Go gate in hub/: go build ./... && go vet ./... && go test ./... → rc 0.

Red-proofs — each guard broken on purpose, its test seen failing, then restored

Evidence: documentation/audits/hub-tokens-2026-10-15/red-proof/ (one file per case; each shows === RUN for the test).

# guard broken test failed with
1 operator-action entry removed from the route table TestRouteTable_MatchesServeHTTPSwitch route 21: switch has /hosts/…/operator-action, table has /request-logs
2 session-only allowed for tokens TestToken_SessionOnlyRoutesRefuseEveryToken POST / with an all-scope token: 200
3 expiry not checked TestToken_NoTokenUnknownExpiredRevoked401 expired token: 200, want 401
4 group:read accepted on a write route TestToken_ScopeDecides floor:read token on floor save: 303; the floor changed
5 plain token stored TestAPIToken_StoredRowHasNoPlainToken t.db holds the plain token
6 old empty-password skip restored TestRequireAuth_NoPasswordRefusesEveryOperatorRoute no password: GET / → 200
7 Basic passes without the header TestToken_CSRFRuleUnchangedForBasic Basic without X-Felhom-Operator: 303
8 handler's own token check removed TestToken_CannotManageTokens handleTokenCreate under a token: 200; 2 rows
9 GET /customers removed from the API table TestAPIRouteTable_MatchesSwitch route 36 mismatch
10 requireGlobalKey returns true TestAPIRoutes_NoCredential GET /customers with no credential: 200
11 fhp_ check removed from the API TestAPI_AccessTokenIsRefusedEverywhere POST /report with an access token: 401 (not the 403 refusal)
12 main mounts ServeHTTP bare TestWebMountAlwaysAuthenticated 0 via Handler(), 1 other

Claims in the task that turned out wrong

  • „Hungarian UI, same style as the page": the Configuration page and the whole hub UI are English (the design system: operator surfaces are English). The card is English, like the page.
  • „Mail hold — read: the held mails": the hub keeps no held mail; a hold drops it (internal/mailhold). Mail hold has only a write tick (release). Releases: vouch has only a write tick too — the Day-0 manifest has no page of its own.
  • „dr-recipe.json if it carries a secret": it carries none (handleDRRecipeDownload: „secret-free"); it is customers:read.
  • Not in the task, found: the customer page embeds the owner passphrase and the controller API key, and the config preview carries the controller key — both session only.

Recommendations not followed (one line each)

  • „Customers write: resend claim" — followed as written, but a stolen customers:write token can then redirect a household's claim code (edit e-mail, resend). Filed R-1055 for your decision; my pick is password-only.
  • „Off-site read: key audit" — followed; it is a POST that runs the check now and changes no key.

Scripts and runbooks moved from the password to the token (Part C4 — done)

where what scope it needs
skills/felhom-build-deploy/SKILL.md release step 5 global floor save floor:write — the CC token has it
skills/felhom-build-deploy/SKILL.md ISO section discard a proof install's appliance session only — no token can; stays yours or stays the password until you place it
~/git/misc-scripts/gitea-image-prune.sh reads floor, golden, agent from the Configuration page floor:read — the CC token has it
documentation/runbooks/RUNBOOK-manual-build.md §prune describes the script above —

Moved: the skill's floor step (the recipe was run verbatim: 303 flash=floor_set), gitea-image-prune.sh (misc-scripts; its test passes, a live dry-run read 6 in-use versions through the token, deleted 0), the runbook line. The appliance discard is password-only: the skill now says to ask you. Nothing CC runs reads HUB_PW any more.

Register

Before 155 · after 156 · opened 3 (R-1054 global API key's future, R-1055 claim-resend chain, R-1056 five routes with no group) · closed 2 (R-952, R-1051).

Teardown

Machine: nothing provisioned. Host: nothing. Hub: v0.152.0 deployed (manifest 2184897); two floor saves with the unchanged value; no test token created by CC (the only token is the operator's). Evidence: documentation/audits/hub-tokens-2026-10-15/ (deploy.txt, C3-token-live.txt, red-proof/).