From e205a2d6a1e03b869cce8a7d5bd0efc756c8fe2d Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Sun, 12 Jul 2026 19:38:42 +0200 Subject: [PATCH] =?UTF-8?q?test:=20claim=20engine=20=E2=80=94=20reset=20re?= =?UTF-8?q?quests=20never=20alter=20claimed=20state=20(Scenario=20C=20non-?= =?UTF-8?q?DoS;=20leftover=20from=20the=20claim-arc=20session)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_01NptTCFtu7dz2Ru89qHRagN --- hub/internal/claim/engine_test.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/hub/internal/claim/engine_test.go b/hub/internal/claim/engine_test.go index 0e6d89d..60f2db4 100644 --- a/hub/internal/claim/engine_test.go +++ b/hub/internal/claim/engine_test.go @@ -202,3 +202,30 @@ func TestIssue_EmailFailureKeepsGateArmed(t *testing.T) { t.Fatalf("emailed_at must stay unset on failure: %+v", row) } } + +// Reset non-DoS (Scenario C WRONG case): repeated reset REQUESTS never touch the box's password +// or claimed state — the engine only rotates the CODE. The controller's password keeps working +// throughout (the box side owns the password; the hub only mails codes). Red-proof partner: make +// RequestReset also clear claimed_at → this fails. +func TestRequestReset_NeverAltersClaimedState(t *testing.T) { + e, st, _ := newTestEngine(t) + if _, err := e.EnsureIssued(cust()); err != nil { + t.Fatalf("EnsureIssued: %v", err) + } + if err := e.MarkClaimed(cust()); err != nil { + t.Fatalf("MarkClaimed: %v", err) + } + claimedBefore, _ := st.GetClaim("c1") + for i := 0; i < 3; i++ { + if err := e.RequestReset(cust()); err != nil { + t.Fatalf("RequestReset %d: %v", i, err) + } + } + claimedAfter, _ := st.GetClaim("c1") + if !claimedAfter.Claimed() { + t.Fatal("reset requests un-claimed the box — must never happen") + } + if !claimedAfter.ClaimedAt.Equal(*claimedBefore.ClaimedAt) { + t.Fatal("reset requests moved claimed_at") + } +}