diff --git a/REPORT.md b/REPORT.md index 17b8b9f..e6b486d 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,252 +1,239 @@ -# REPORT — a secret leaves the page source, and two refusals learn to say what to do (v0.207.0, 2026-08-08) +# REPORT — the last two secrets leave the page source, and a gate against a fourth (v0.208.0, 2026-08-08) -**R-249, R-252, R-253 — implementation. R-201's record corrected, R-254 filed. Controller -`v0.206.0` → `v0.207.0`, built, pushed and deployed to `demo-hp` guest 9201 and VM 325.** No hub -change was needed. Baselines re-read on arrival and all four repos matched the task exactly -(`felhom-controller 3d3b4496f377`, `felhom.eu 3f4fb3825f06`); highest register ID was **R-253**. +**R-254 both sites — implementation, tests, deploy. R-255 filed. Controller `v0.207.0` → `v0.208.0`, +built, pushed, deployed to `demo-hp` guest 9201.** No hub change. Baselines re-read on arrival: +`felhom-controller 62998aab4fec` (matches the task), `felhom.eu f651b31a7a8f` — **one commit ahead of +the task's `c1dec41`, which is exactly the golden bake+vouch the task says was running as it was +written.** Highest register ID on arrival: **R-254**. + +**§1's question answered: the bake COMPLETED.** Vouched on arrival — `golden_version 0.207.0`, +`agent_version 0.127.0`, `min_agent 0.127.0`. So `v0.208.0` puts the currency gate red again (§7.5). --- -## 1. Scenario A — the raw response body, before and after +## 1. Scenario A — and the honest limit on the live half -**This is the deliverable that matters, and it was measured on a live box with a positive control in -both directions.** A known passphrase was planted on VM 325 (a throwaway due for destruction the same -hour) and the security settings page fetched exactly as the defect was originally found — with `curl`. +**Unit level, on the real template and the real handler — this is the strong evidence:** -| | v0.206.0 (the shipped defect) | v0.207.0 (the fix) | +| | before (v0.207.0 shape) | after (v0.208.0) | |---|---|---| -| page bytes | 50 208 | 51 115 | -| **positive control** — card renders (a passphrase IS stored) | **1** | **1** | -| reveal control present | — | **2** | -| **passphrase occurrences in the response body** | **1** | **0** | +| `{{.InitialCreds.Password}}` in `app_info.html` | present, inside a `hidden` span | **absent** | +| the password in the rendered body | **present** | **absent** | +| the card, username, note | present | present (feature intact) | +| reveal control | reads the hidden span | calls `POST /apps//initial-credentials/reveal` | -**The positive control is load-bearing.** The first attempt at this check returned `0` on a box that -had no passphrase stored at all — a zero that meant nothing. That is why the card's presence is -asserted first: *an empty listing is not evidence of emptiness.* - -**Scenario B, live:** `POST /settings/retrieval-password/reveal` → **200**, `Cache-Control: no-store`, -body 85 bytes carrying the correct passphrase (**1** match against the planted value). The controller -logged `retrieval passphrase revealed via the security page from 192.168.0.100 (value never logged)`, -and a grep of five minutes of controller log for the value itself returned **0**. - -### The red-proof — the most important one here - -Both halves of the fix were reverted (`data["RetrievalPassword"] = …` restored in `handlers.go`, and -`{{.RetrievalPassword}}` put back inside a `display:none` span), **each mutation asserted to have -applied** before running: +**Red-proof (the most important one):** the value was put back into *both* the page data and the +markup — each mutation asserted to have applied — and the test failed exactly as designed: ``` -BOTH MUTATIONS APPLIED — verified by string change ---- FAIL: TestSecurityPage_DoesNotContainTheRetrievalPassphrase (0.08s) - R-249: the retrieval passphrase is in the response body of the security page … +MUTATION A APPLIED (template + handler) +--- FAIL: TestAppInfoPage_DoesNotContainTheInitialPassword + R-254: the app's first-login password is in the response body of its info page … ``` -Restored; residue checked (`{{.RetrievalPassword}}` occurrences: **0**); green again. +### ⚠ THE LIVE READ COULD NOT BE COMPLETED, AND HERE IS EXACTLY WHAT WAS TRIED -**Why the tests assert the body and not a view:** the old markup hid the value with CSS. Every test -that asked *what does the customer see* passed while the bytes carried the secret. A test that cannot -see a `display:none` span cannot see this defect at all. +§10.1 asks for a `curl` of an app's info page **on a box where a generated credential genuinely +exists**. Two independent things blocked it, both measured: + +1. **No such app is deployed anywhere on the fleet.** `crafty-controller` is the **only** catalog app + declaring `initial_credentials`; on `demo-hp` it has a template directory but **no `app.yaml` and no + containers**. `demo-felhom` reported **zero** stacks. +2. **The dashboard on `demo-hp`'s guest would not authenticate.** `PASSWORD` from + `~/.config/credentials` (13 bytes, quotes stripped, passed file→file), POSTed to `/login` at the + container IP with the mandatory `Host: felhom.enkisfelhom.hu` header → **HTTP 200 with no + `felhom_session` cookie**, which is a failed login (a success is 302 + `Set-Cookie`). + +**A probe I tried and discarded, because its control killed it:** POSTing the two new routes +unauthenticated to prove they exist. Real routes and a deliberately bogus path **all returned 302** — +the auth middleware answers before dispatch, so the probe cannot distinguish them. **Reported as +worthless rather than as evidence.** + +**What IS confirmed live:** `gitea.dooplex.hu/admin/felhom-controller:0.208.0` deployed and healthy on +`demo-hp` guest 9201. + +**I could have manufactured the missing conditions** — deploy `crafty-controller` and reset a demo +box's dashboard password. I did not: the second changes an operator credential on a reference box for +the sake of a screenshot. The unit evidence covers the same assertion on the same template and +handler, and the identical shape *was* live-verified yesterday for R-249 with a before/after. --- -## 2. §7.1's census — the pattern found once, looked for everywhere +## 2. §7.2 — what reaches the deploy form's hidden inputs -**It was worth doing: two more instances, one with a real secret.** +| Question | Answer | +|---|---| +| Which env values reach it? | **Only `type: secret` auto-generated fields, and only PRE-DEPLOY** (`{{if and (not $isDeployed) (eq .Type "secret")}}`) | +| Are any of them secrets? | **Yes** — generated from the catalog's `generate:` spec | +| Must the form carry them? | **Yes, and this is documented.** `README §318`: the values are pre-generated so the customer can note them down, and submitting them back is what makes the saved value **the same one they saw** — *"no silent re-generation on submit"*. A form must carry what it submits. | +| Does it contradict the repo's auto-fill rule? | **No — and the rule as quoted does not exist.** The task cited *"Password fields require explicit user input or generation (no silent auto-fill)"*. **No such line is anywhere in the repo.** What exists is `CONTEXT.md:2070` — *"Password fields require explicit input \| Prevents accidental empty-password deployments"* — which is about **emptiness**. The hidden input does not contradict it; if anything it serves the same end. | -| Where | What | Verdict | -|---|---|---| -| **`app_info.html:185`** | an app's auto-generated first-login password in `` beside a „Megjelenítés" button | **the same defect with a real per-install secret** — `ReadInitialCredentials` reads it live out of the deployed container, so it is *not* the catalog's published `default_creds` | -| `deploy.html:482` | an auto-generated `type: secret` field in `` | weaker — the **pre-deploy** form must post the value; on an **already-deployed** app's page the exposure is gratuitous | +**So the hidden input was left alone.** What §7.2 uncovered instead is the neighbouring **readonly +display input**: on an already-deployed app the hidden input is correctly omitted — nothing is being +submitted — yet `` still rendered the secret into a +page the customer merely opens. **That is R-249's shape with no form to justify it, and it is what +v0.208.0 fixes** (`POST /stacks//auto-field/reveal`, authorised on the field being a +`type: secret` auto-field *of that stack*). -**Filed as R-254, not fixed** — scope was R-249/R-252/R-253, and each needs its own reveal endpoint -and its own body-asserting test rather than a shared quick edit. **Recommended next**, because R-249 -proved the pattern is not theoretical. - -**Rotation advice, left to the operator:** the `walk5` instance is compromised (it reached a -transcript) and dies with that customer's teardown. **No other instance is known to have been read — -but nothing records a read, which is itself part of the defect**, and that is the honest state of the -evidence. If any customer page has been screen-shared, saved or proxied, treat that passphrase as -exposed. +**Both directions are pinned**, because "fixed" here means one branch changed and the other did not: +`TestDeployPage_DeployedApp_DoesNotCarryTheSecret` and +`TestDeployPage_PreDeployForm_StillCarriesTheValue_Deliberately`. --- -## 3. §7.2's decision — which half of the reinstall promise was wrong +## 3. §7.4 — the guard: what it cost, what it covers, what it does not -**The promise was wrong, and the reason is structural rather than a matter of effort.** +**Built:** `controller/scripts/secret_in_markup_gate.py`, registered in `controller_gates.py` (now ten +gates). ~120 lines, no scaffolding, runs over **all 36 templates** in well under a second. -`reconstituteOffbox` refuses when `GetStackHDDPath(stack)` is empty, because **the destination of a -restore is the app's own data path — a drive the CUSTOMER chooses at deploy time.** An automatic -reinstall would mean the product picking that drive for them, which is precisely the decision this -recovery path exists to leave with them (`DOMAIN` and `SUBDOMAIN` would have to be invented too). +**What it catches** — any `{{ … }}` whose expression names a secret, unless allowlisted with a stated +reason. **Verified by planting one:** the gate convicted `app_info.html:188`. It **also** catches a +launder through a local variable, because the assignment names the secret +(`{{$v := .InitialCreds.Password}}` convicted). -So the smaller change is also the correct one: the copy now says to install first and routes there. -The row's own code comment already read *"restore in place vs. reinstall first"* — the behaviour was -right and the sentence had drifted. +**What it does NOT catch, measured rather than estimated:** a secret arriving under a **neutral +page-data key**. Planted `data["Tagline"] = ` rendered as `{{.AppInfo.Tagline}}` — **the gate +passed cleanly**, and the runtime body assertion caught it. **That is the shape of R-254 site two, so +this gate would not have caught one of the three instances it was written for.** + +**The complementary net** — render with a sentinel, grep the response — catches every shape, but needs +each page's data constructible. **4 of 27 page templates have that today** (`settings_security`, +`app_info`, `deploy`, `backups_restore` — the four touched by R-249/R-252/R-253/R-254). **The other 23 +have no runtime coverage.** + +**I did not build the remaining 23 and did not describe the guard as complete** — §7.4 is explicit that +a partial guard reported as complete is worse than none. **Filed as R-255** with the cost stated: a +data fixture per page (most need a wired `Server`), then one table-driven sentinel test. --- -## 4. Every changed Hungarian string, for review as copy +## 4. §7.3 — what has actually been exposed, and whether to rotate -**Removed** (`backups_restore.html`) — the promise the restore could not keep: +**Site one: the code path has never run in production.** The card renders only when +`found.Deployed && found.Meta.InitialCreds != nil`; `crafty-controller` is the only app declaring +`initial_credentials` and is **deployed nowhere**. -> „Nincs telepítve — a visszaállítás előbb újratelepíti." +**Site two: also empty, as measured.** 26 catalog apps declare a generated `type: secret` field, but +`demo-hp` has exactly **three** apps deployed — `calibre-web`, `opengist`, `privatebin` — and **none of +them declares one**. Intersection: **zero**. -**Added** (`backups_restore.html`, the not-installed row): +> **THE LIMIT, stated because it changes what this is worth.** That is a **current-state** +> measurement. An app deployed and later removed would not appear in it, and **nothing anywhere +> recorded a read** — which is part of the defect being fixed. So the honest finding is **"no evidence +> of exposure"**, not "proof there was none", **and no mechanism existed that could have produced +> evidence either way.** -> „Nincs telepítve — előbb **telepítsd újra**, utána hozhatod vissza az adatait." *(„telepítsd újra" -> links to `/stacks//deploy`)* - -**Added** (`backups_restore.html`, the precondition notice — renders only when no drive is registered): - -> „**Előbb csatold vissza az adatmeghajtót.** A mentéseid megvannak, és a meghajtók is megvannak — -> újratelepítés után viszont a gép még nem ismeri őket, ezért most nincs hová visszaállítani. Ez két -> kattintás: **Tárhely → Meghajtók**, „Meglévő meghajtó csatolása". Utána gyere vissza ide." - -**Changed** (`offbox_restore.go`, the drive refusal): - -> was: „nincs elérhető adatmeghajtó a visszaállításhoz" -> now: „nincs regisztrált adatmeghajtó, ezért nincs hová visszaállítani — a meghajtók megvannak, csak -> újra kell csatolni őket a Tárhely → Meghajtók oldalon, utána ez a visszaállítás működni fog" - -**Changed** (`offbox_reconstitute.go`, the not-installed refusal): - -> was: „a(z) %s nincs telepítve — előbb állítsd helyre az alkalmazást, utána az adatokat" -> now: „a(z) %s nincs telepítve, ezért nincs hová visszaállítani az adatait — telepítsd újra az -> alkalmazást (Alkalmazások), utána ez a visszaállítás működni fog" - -**Added** (`settings_security.html`, reveal failure): „A visszaállítási jelszó lekérése nem sikerült." -**Added** (reveal endpoint, nothing stored): „Ezen a gépen nincs tárolt visszaállítási jelszó." +**Recommendation: rotation is not indicated by anything measured.** The decision is the operator's; +this note is the input to it. --- -## 5. Tests and red-proofs — each mutation asserted to have applied +## 5. Every changed Hungarian string -| Test | Scenario | Result | Red-proof (mutation → observed) | +**Added** (`app_info.html`, reveal failure): the message comes from the server — +> „A kezdeti jelszó most nem olvasható ki — az alkalmazásnak futnia kell hozzá, és lehet, hogy a fájlt az első bejelentkezés után már törölték." + +**Added** (reveal endpoint, other refusals): „Az alkalmazáskezelő nem elérhető." · „Ismeretlen +alkalmazás." · „A kezdeti jelszó beolvasása nem sikerült." + +**Added** (`deploy.html` / auto-field reveal): „Ez a mező nem kérhető le." · „Az alkalmazás +beállításai nem olvashatók." · „Ehhez a mezőhöz nincs mentett érték." · „Hiányzó mező." · „A lekérés +nem sikerült." + +**Unchanged and deliberately so:** „Megjelenítés" / „Elrejtés" / „Másolás" / „Másolva", and +„Kezdeti belépési adatok" — the controls and the card keep their words; only where the value comes +from changed. + +--- + +## 6. Tests and red-proofs — each mutation asserted to have applied + +| Test | Scenario | Result | Red-proof | |---|---|---|---| -| `TestSecurityPage_DoesNotContainTheRetrievalPassphrase` | **A** | PASS | value re-rendered into the page → **FAIL**, printing the exposure | -| `TestSecurityPage_NoCardWhenNoPassphraseStored` | A | PASS | — (gate is on existence, not the value) | -| `TestRevealEndpoint_ReturnsThePassphraseToAnAuthenticatedCaller` | **B** | PASS | remove the route/handler → customer cannot obtain it at all | -| `TestRevealEndpoint_404sWhenNothingStored` | B | PASS | — | -| `TestRestorePage_NoRegisteredDrive_NamesTheReasonAndTheRoute` | **C** | PASS | precondition block deleted → **FAIL** | -| `TestRestorePage_HealthyBox_HasNoPreconditionNotice` | **E** | PASS | notice made unconditional (`{{if true}}`) → **FAIL** | -| `TestRestorePage_NotInstalled_DoesNotPromiseAReinstall` | **D** | PASS | old promise restored → **FAIL on all three assertions** | -| `TestHasRestoreDestination_AgreesWithTheResolver` | seam | PASS | predicate forced `true` → **FAIL** | +| `TestAppInfoPage_DoesNotContainTheInitialPassword` | **A** | PASS | value restored to page data + markup → **FAIL** | +| `TestAppInfoPage_NoCredentialsCard_WhenAppHasNone` | **E** | PASS | card made unconditional → **FAIL** | +| `TestAppInitialCredsReveal_ReturnsThePasswordAndRecordsTheAct` | **B + C** | PASS | logging line removed → **FAIL** ("the reveal was not recorded") | +| `TestAppInitialCredsReveal_SaysWhyWhenUnreadable` | §7.1 | PASS | — | +| `TestDeployPage_DeployedApp_DoesNotCarryTheSecret` | **D** | PASS | `{{if $isDeployed}}` branch dropped → secret returns | +| `TestDeployPage_PreDeployForm_StillCarriesTheValue_Deliberately` | **D** | PASS | — (guards the half that must NOT change) | +| `secret_in_markup_gate.py` | **F** | PASS | secret planted in markup → **gate convicted it** | -**Red-proof D caught a fault in my own work.** On its first run only two of its three assertions -failed, because **the explanatory HTML comment I had written quoted the old sentence — and HTML -comments ship in the response body.** The contradiction was still on the page, and the assertion that -forbids it could never fail. The comment was rewritten to describe the old string without quoting it; -re-run, all three assertions fail under the mutation. *This is the same class as the defect being -fixed, and it was found only because the red-proof was actually run.* +**Two things the red-proofs caught in my own work, which is the point of running them:** -**One pre-existing test was updated, not deleted:** `TestOffsiteRestoreList_RenderedForRebuiltBox` -pinned the old sentence. Its intent — *state the not-installed consequence, do not hide it* — is -unchanged; it now asserts the consequence **and** the route. +1. **A fixture bug that would have made three assertions pass for the wrong reason.** The credentials + card lives inside `{{if .HasAppInfo}}`; my first fixture omitted it, so the card never rendered and + "the password is absent" was true trivially. Caught because the test *also* asserts the card and + the username are present. +2. **An assertion that matched the page script instead of the control.** `auto-field/reveal` appears in + the JS function body on both variants, so a substring match reported a control that was not there. + Tightened to the `onclick="revealAutoField('vaultwarden','DB_PASSWORD'` markup. -**Green gate:** `go build ./... && go vet ./... && go test ./...` → **rc 0**. -**`controller_gates.py`:** all nine **OK**. +### A correction to v0.207.0's report + +It stated that **HTML comments ship in the response body**. **They do not, here.** This package renders +with `html/template`, which **strips** comments — measured directly: `text/template` keeps them, +`html/template` does not. A red-proof that plants a secret in a comment therefore *correctly* does not +fail, which is what a red-proof of mine did today before I checked the assumption. Corrected in the +test's own comment, the CHANGELOG and `CONTEXT.md`. + +**Green gate:** `go build && go vet && go test ./...` → **rc 0**. **`controller_gates.py`:** ten gates, +all **OK**. --- -## 6. §7.5 — the currency gate went red, as predicted +## 7. §7.5 — the currency gate -``` -newest released controller : 0.207.0 -newest golden baked : 0.206.0 -GOLDEN CURRENCY GATE FAILED -``` +Red, as designed: released controller **0.208.0**, newest vouched/baked golden **0.207.0**. The +`felhom-controller` push needed no bypass (its own gates passed); the **`felhom.eu` push used +`git push --no-verify`**, declared in that commit, in `CHANGELOG.md`, here, and in **R-242's row**. -**The `felhom-controller` push needed no bypass** — that repo's own gates passed. **The `felhom.eu` -push used `git push --no-verify`**, declared in that commit message, in this report, and in -**R-242's row**. - -**A bypass and NOT a waiver, deliberately.** The gate offers a waiver for a release that *deliberately -needs no golden*; this release needs one. Recording a waiver would have made the register say -something untrue. **Owed: bake golden 0.207.0 and vouch it.** +**A bypass, not a waiver** — the gate offers a waiver only for a release that *deliberately* needs no +golden, and this one needs one. **Owed: bake golden 0.208.0 and vouch it.** Two releases, two bakes +owed inside 24 h — which is itself the argument for R-242's untouched half: **nothing gates the +vouch.** --- -## 7. Part 3 — the record +## 8. Files, commits, deployed version -**R-201's state field read `PASSED + PROVEN-LIVE 2026-08-04` — the *data* half, from the drill** — -while the row body carried the fifth walk's journey pass. Corrected to -**`BOTH HALVES PASSED — DATA 2026-08-04, JOURNEY 2026-08-07`**, with what it does **not** claim stated -in the field itself: the journey is not smooth, and the discriminator's positive half is unproven. +**`felhom-controller` — `27d1165`** (v0.208.0): `internal/web/handlers.go` · `internal/web/server.go` · +`internal/web/templates/app_info.html` · `internal/web/templates/deploy.html` · +**new:** `internal/web/app_initcreds_exposure_test.go`, `internal/web/deploy_secret_exposure_test.go`, +`scripts/secret_in_markup_gate.py` · `scripts/controller_gates.py` · `CHANGELOG.md` · `CONTEXT.md` · +`REUSE.md` · `controller/README.md`. -**The census asked for: a sweep of all 146 register rows found NO OTHER state field disagreeing with -its own body.** Two candidates (R-229, R-230) were **false positives** — per-*leg* closes on rows that -legitimately remain open. +**`felhom.eu` — `59527d0`** (pushed `--no-verify`, declared): `documentation/backlog/OPEN-ITEMS.md` +(R-254 closed per site, **R-255** filed, R-242 updated) · `STATUS.md` (94 lines). -**So the pattern is real but not currently widespread**: R-218 on 2026-08-06, R-201 now, nothing else -outstanding. **The mechanism is worth naming, because it will recur:** a row states status in *two* -places — a bold phrase early in the What column and the State cell at the end — and a session that -closes an item updates the body and the end cell, while the early phrase, **which is what a reader -sees first**, goes stale. +**Deployed:** `gitea.dooplex.hu/admin/felhom-controller:0.208.0` — healthy on `demo-hp` guest 9201. + +**Register:** highest ID moved **R-254 → R-255**. + +**§N.5 capability map — checked, not assumed:** no row claims a secret-handling property about pages or +markup (`0` matches for "never templated / not in the page / page source / markup"). **No change made.** + +**Teardown:** nothing was provisioned this session — nothing to tear down. --- -## 8. Live validation — what was proven on a box, and what was not +## 9. What remains open -| # | Check | Result | -|---|---|---| -| 1 | `curl` the security page, passphrase absent | **PASS, live, with before/after** (§1) | -| 2 | the reveal works for a customer who asks | **PASS, live** — 200, `no-store`, correct value, logged | -| 3 | the two refusals rendered on VM 325 | **NOT PROVEN LIVE — stated plainly** (below) | -| 4 | a healthy box's restore page unchanged | **PASS, live** — no notice, no not-installed hint | +- **R-255** — runtime sentinel coverage for the other 23 page templates. +- **R-242** — bake + vouch golden 0.208.0; and its untouched half, that nothing gates the vouch. +- **The fingerprint discriminator's positive half** — still unproven; it needs a deliberate fixture (a + box holding a divergent key), not a walk. +- Untouched by design: **R-250, R-251, R-243, R-244, R-240, R-247, R-248, R-213, R-202, R-214.** -**Check 3 is the honest gap.** R-252's state is **rebuild-only**: the customer API **refuses to -deregister the last usable drive** („ez az egyetlen használható tárhely — a leszerelés megtagadva"), -and when `storage_paths` was emptied directly the controller re-registered one on restart, so -`HasRestoreDestination()` was correctly `true` and the notice correctly did not render — **which is -Scenario E passing live, not a failure.** R-253 likewise: after removing the app the controller still -reports `deployed=true` (state `stopped`), so the row's `Installed` stays true. +## 10. Observations — noticed, NOT acted on -**Both notices are therefore pinned at the template + predicate level** — by tests that drive the real -template and the real predicate, each with a demonstrated red-proof — **and not by a live render.** I -would rather say that than dress the template test up as a live one. - ---- - -## 9. Files, commits, deployed version - -**`felhom-controller` — `8dbbc98`** (v0.207.0): -`internal/web/handlers.go` · `internal/web/server.go` · `internal/web/templates/settings_security.html` · -`internal/web/templates/backups_restore.html` · `internal/backup/offbox_restore.go` · -`internal/backup/offbox_reconstitute.go` · `internal/web/offsite_restore_list_test.go` · -**new:** `internal/web/retrieval_password_exposure_test.go`, `internal/web/restore_preconditions_test.go`, -`internal/backup/restore_destination_test.go` · `CHANGELOG.md` · `CONTEXT.md` · `REUSE.md` · -`controller/README.md` - -**`felhom.eu` — `1fc3876`** (pushed `--no-verify`, declared): `documentation/backlog/OPEN-ITEMS.md` -(R-249/R-252/R-253 closed, **R-254** filed, R-201's state field corrected, R-242 updated) · `STATUS.md` -(93 lines). - -**Deployed:** `gitea.dooplex.hu/admin/felhom-controller:0.207.0` — healthy on **demo-hp guest 9201** -and on **VM 325**. - -**Register:** highest ID moved **R-253 → R-254**. - ---- - -## 10. What remains open - -- **R-254** — the two remaining render-then-hide instances. **The recommended next item.** -- **R-242** — the bake + vouch of golden 0.207.0 this release now needs; and R-242's own half, that - nothing gates the *vouch*. -- **The discriminator's positive half** — the fifth walk exercised R-241's mint guard positively and - the fingerprint comparison only negatively. **Proving the positive half needs a deliberate fixture - (a box holding a divergent key), not a walk** — and v0.206.0's guard now prevents that state arising - by itself, which is exactly why it needs constructing. -- Untouched by design: **R-250, R-251, R-243, R-244, R-240, R-213, R-202, R-214, R-247, R-248.** - -## 11. Observations — noticed, not acted on - -- **`shares_restore.go:62,81` carries the same „nincs elérhető adatmeghajtó" string** with no route, - for the SMB-shares restore path. Out of scope (R-252 is the app path); the same one-line treatment - would fix it. -- **The reveal endpoint has no rate limit.** It is behind session auth + CSRF, so it is not a - brute-force surface, but the escrow start handler deliberately rides the login limiter and this does - not. Deliberate omission, flagged. -- **The Hetzner token in `~/.config/credentials` cannot see storage box 611421** — `GET - /v1/storage_boxes/611421/subaccounts` → 404 and `GET /v1/storage_boxes` → 200 with **0 entries**. It - is scoped to a different project than the hub's. Recorded so the next teardown does not re-derive it. +- **`demo-felhom`'s guest reported zero deployed stacks**, and one `ssh` to it failed (exit 255) while + probing. Not investigated — out of scope, but it means the fleet has effectively one box carrying + apps, which is worth knowing before the next test that assumes two. +- **`demo-hp`'s dashboard password in `~/.config/credentials` no longer authenticates** (§1 above). + Whatever changed it, the recorded credential is stale — that will block the next session that needs + that UI. +- **The reveal endpoints have no rate limit.** Behind session auth + CSRF, so not a brute-force + surface; the escrow start handler deliberately rides the login limiter and these do not. Flagged, as + it was for v0.207.0's endpoint. +- **`crafty-controller` is the only app with `initial_credentials` and it is installed nowhere** — the + feature has no live coverage at all, which is why §10.1's live check had nothing to read.