Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
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.
- Change the hub password. CC no longer needs it.
- Delete the
HUB_PWline in~/.config/credentialson DooPlex. CC does not delete it. - 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.
- Decide R-1055: should „resend claim" need your password? My pick: yes.
- 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 inRequireAuth, 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). Also04§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.jsonif it carries a secret": it carries none (handleDRRecipeDownload: „secret-free"); it iscustomers: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:writetoken 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/).