b03a105375
gates / gates (push) Successful in 17s
Hub only. No controller change, no agent change, no wire change — nothing to bake. demo-hp untouched: the operator is re-deploying it this evening. R-323 — the five-word phrase is „Tulajdonosi jelmondat". It was „Visszaállító jelszó": one word from the name retired last week, and false besides — it restores nothing, it proves the account owns the box being bound. Five sites, all in the hub; felhom-controller and felhom-agent carry the name nowhere, so no halt and no bake. Both suggested names were rejected with reasons: „Fiókjelszó" would collide with the dashboard login (a DIFFERENT real secret), and „Összekötési jelszó" would leave the two factors on this page separated only by kód-versus-jelszó — the exact shape being removed, since the other factor is the „Párosító kód". The chosen name differs on both axes, stem and noun. Naming only; the acceptance pin drives the real handler. R-324 — the hub's customer copy is under a guard for the first time. Retired names banned across all 95 hub files; retrieval stems registered in four declared customer surfaces. The selftest found a defect in its own instrument on the first run. One shared vocabulary in scripts/, drift-checked into the controller gate rather than copied (R-325 removes the scaffold). R-321 — a machine we told to be quiet is no longer reported as dead, and it was two doors, not one: because the state is RECORDED rather than deleted, the morning deadline check can skip it too. A deleted state returns "", which is not "down" — R-195's shape returning through a second door. The clock runs from the report the hub can see, so re-enabling starts it there and emits no recovery for an outage that never happened. Three red-proofs; the one that matters showed a genuinely dead machine sitting at "disabled" when the suppression was made unconditional. R-326 — "which claims are unproven" is answerable by a command now. The nine I have been repeating was the count of claims the 9 August pass DOWNGRADED, not the count of unproven ones. The real figures: 55 claims, 23 walked, 32 not — and only 6 of those 32 cite evidence. Its first run found a stale claim (R-327).
133 lines
6.4 KiB
Go
133 lines
6.4 KiB
Go
package web
|
|
|
|
// R-323 — THE THIRD NAME. One secret, one name, and none of them a near-homograph of another.
|
|
//
|
|
// Three secrets a customer can hold, and they must be tellable apart by a hurried reader:
|
|
//
|
|
// three words takes control of the dashboard „Beállító kód"
|
|
// ten words opens the sealed off-site backups „Helyreállítási kód"
|
|
// five words proves the account owns the box being bound „Tulajdonosi jelmondat" ← this one
|
|
//
|
|
// The five-word phrase used to be „Visszaállító jelszó" — one word away from „Visszaállító kód",
|
|
// which R-295 had just retired for colliding with „Helyreállítási kód". Found while shipping that
|
|
// rename and deliberately NOT swept in with it; ruled on separately by the operator 2026-08-13.
|
|
//
|
|
// WHY NOT THE OBVIOUS CANDIDATES, since a test is where the reasoning survives:
|
|
//
|
|
// „Összekötési jelszó" — the OTHER factor on this very page is the „Párosító kód". Naming this one
|
|
// after the same act would leave the two factors a customer types in one sitting separated
|
|
// only by kód-versus-jelszó, which is structurally the „Visszaállító kód"/„Visszaállító
|
|
// jelszó" trap being removed.
|
|
// „Fiókjelszó" — there IS an account password (the dashboard login). This would collide
|
|
// with a DIFFERENT real secret, i.e. trade one homograph for a worse one.
|
|
//
|
|
// „Tulajdonosi jelmondat" is distinct on BOTH axes — stem (Tulajdonosi vs Beállító / Helyreállítási
|
|
// / Párosító) and noun (jelmondat vs kód / jelszó) — and it says what the phrase actually does.
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-hub/internal/notify"
|
|
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
|
|
)
|
|
|
|
// retiredThirdName is the name this change removes. It is a SUBSTRING check on the stem so that a
|
|
// possessive or accusative form („visszaállító jelszavadat") cannot slip past a check written
|
|
// against the nominative — the R-299 lesson, where a guard matched one inflection of a Hungarian
|
|
// verb and the plural walked straight by.
|
|
const retiredThirdName = "isszaállító jelsz"
|
|
|
|
// The binding page names the secret „Tulajdonosi jelmondat" and nowhere carries the retired name.
|
|
// Driven through the real handler, so a template that never renders is visible here.
|
|
func TestSelfBind_ThirdSecretNaming(t *testing.T) {
|
|
s, st := newTestServer(t)
|
|
selfBindSetup(t, st, "acme", testCode)
|
|
token := mintLink(t, st, "acme", selfBindTTL)
|
|
|
|
body := bindGET(t, s, token).Body.String()
|
|
|
|
if !strings.Contains(body, "Tulajdonosi jelmondat") {
|
|
t.Errorf("the field label must name the secret „Tulajdonosi jelmondat”:\n%s", body)
|
|
}
|
|
if strings.Contains(body, retiredThirdName) {
|
|
t.Error("the retired name „Visszaállító jelszó” is back on the binding page")
|
|
}
|
|
// The two factors on this page must not be near-homographs of each other. The pairing code keeps
|
|
// its name; what matters is that the SECOND factor no longer differs from it by one noun.
|
|
if !strings.Contains(body, "Párosító kód") {
|
|
t.Error("the pairing code lost its name — the two factors must both be named")
|
|
}
|
|
// Neither of the OTHER two secrets may be named on this page: a customer who reads „kód" here
|
|
// twice is the failure this whole arc exists to prevent.
|
|
for _, other := range []string{"Beállító kód", "Helyreállítási kód"} {
|
|
if strings.Contains(body, other) {
|
|
t.Errorf("the binding page names a different secret (%q) — that is the collision", other)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The failure banner must name the secret the same way the label does. A page that asks for a
|
|
// „Tulajdonosi jelmondat" and then complains about „a jelszó" has two names for one secret again,
|
|
// which is the defect in miniature.
|
|
func TestSelfBind_FailureBannerUsesTheSameName(t *testing.T) {
|
|
s, st := newTestServer(t)
|
|
selfBindSetup(t, st, "acme", testCode)
|
|
token := mintLink(t, st, "acme", selfBindTTL)
|
|
|
|
rr := bindPOST(t, s, token, testCodeFmt, "wrong wrong wrong wrong wrong")
|
|
body := rr.Body.String()
|
|
|
|
if !strings.Contains(body, "tulajdonosi jelmondat") {
|
|
t.Errorf("the failure banner does not name the secret the way the label does:\n%s", body)
|
|
}
|
|
if strings.Contains(body, retiredThirdName) {
|
|
t.Error("the retired name is on the failure banner")
|
|
}
|
|
}
|
|
|
|
// The e-mail that carries the link names it identically. The mail is read BEFORE the page, so a
|
|
// mismatch here is the customer's first impression of two different secrets.
|
|
func TestSelfBindEmail_UsesTheSameName(t *testing.T) {
|
|
subject, body := notify.FormatSelfBindEmail("acme", "https://hub.example/bind/tok")
|
|
|
|
if !strings.Contains(body, "tulajdonosi jelmondatodat") {
|
|
t.Errorf("the self-bind mail does not name the secret „Tulajdonosi jelmondat”:\n%s", body)
|
|
}
|
|
if strings.Contains(subject+body, retiredThirdName) {
|
|
t.Error("the retired name is still in the self-bind mail")
|
|
}
|
|
// It must still say what the thing IS — a rename that leaves the customer unable to recognise
|
|
// what they are holding has fixed a collision and broken the delivery.
|
|
if !strings.Contains(body, "5 szóból álló kifejezést") {
|
|
t.Error("the mail no longer says the phrase is five words")
|
|
}
|
|
}
|
|
|
|
// THE ACCEPTANCE PIN — the whole point of calling this naming rather than function.
|
|
//
|
|
// The SAME phrase, typed the same messy way a human types it (odd spacing, mixed case, mixed
|
|
// separators), still binds the appliance after the rename. If this ever goes red, the rename stopped
|
|
// being a rename.
|
|
func TestSelfBindPassphrase_StillAcceptedAfterTheRename(t *testing.T) {
|
|
s, st := newTestServer(t)
|
|
id := selfBindSetup(t, st, "acme", testCode)
|
|
token := mintLink(t, st, "acme", selfBindTTL)
|
|
|
|
rr := bindPOST(t, s, token, testCodeFmt, " Alpha Beta gamma-delta epsilon ")
|
|
|
|
if rr.Code != http.StatusOK || !strings.Contains(rr.Body.String(), "egy percen belül") {
|
|
t.Fatalf("the same passphrase stopped being accepted: code=%d body=%q", rr.Code, rr.Body.String())
|
|
}
|
|
if a, _ := st.GetAppliance(id); a == nil || a.Status != store.ApplianceBound {
|
|
t.Fatalf("the appliance did not bind: %+v", a)
|
|
}
|
|
// The form field NAME is deliberately still `passphrase`: renaming customer copy must not touch
|
|
// the wire. A changed field name would be an acceptance change wearing a rename's clothes.
|
|
body := bindGET(t, s, mintLink(t, st, "acme", selfBindTTL)).Body.String()
|
|
if !strings.Contains(body, `name="passphrase"`) {
|
|
t.Error("the form field name changed — that is not a rename, that is a wire change")
|
|
}
|
|
}
|