diff --git a/hub/CHANGELOG.md b/hub/CHANGELOG.md index 7014ab23..36e53c18 100644 --- a/hub/CHANGELOG.md +++ b/hub/CHANGELOG.md @@ -3,6 +3,8 @@ - Found live on 0.176.0 (Tester 1's host page): no button — the agent's host report carries no controller version, so the version-gated button never rendered. Every guest of a customer host has it now; the agent refuses a guest it did not provision. Test `TestHostPage_RestartRendersForAGuestWithNoReportedVersion` (red on 0.176.0's template). +- A press through an access token is recorded as `access token "" from
` (was „operator CLI (basic auth)" + — seen on the live presses). Test `TestOperatorActor_NamesTheAccessToken` (red first). ## v0.176.0 (2026-10-29) — „Restart controller" and „Clear hold" on the host page (D64 = B); controller_unresponsive (R-1344) — DEPLOYED on the operator's yes (image sha256:53372198…, from `3d0c907b`, CI 2503) diff --git a/hub/internal/web/host_opactions_test.go b/hub/internal/web/host_opactions_test.go index 3950848a..0939c593 100644 --- a/hub/internal/web/host_opactions_test.go +++ b/hub/internal/web/host_opactions_test.go @@ -1,7 +1,9 @@ package web import ( + "context" "net/http" + "net/http/httptest" "net/url" "strings" "testing" @@ -109,3 +111,14 @@ func TestHostPage_RestartRendersForAGuestWithNoReportedVersion(t *testing.T) { t.Fatal("no Restart controller for a guest whose controller version the agent does not report") } } + +// Live 2026-10-29: presses through CC's access token were recorded as „operator CLI (basic auth)". A press is named by +// its channel; a token press names the token (never its secret). RED-PROOF: seen failing on 0.176.0's operatorActor. +func TestOperatorActor_NamesTheAccessToken(t *testing.T) { + r := httptest.NewRequest(http.MethodPost, "/hosts/h1/operator-action", nil) + r.Header.Set("X-Forwarded-For", "10.9.8.7") + r = r.WithContext(context.WithValue(r.Context(), tokenCtxKey{}, &store.APIToken{Name: "CC_HOSTS"})) + if got := operatorActor(r); !strings.Contains(got, `access token "CC_HOSTS"`) || strings.Contains(got, "basic auth") { + t.Fatalf("actor = %q", got) + } +} diff --git a/hub/internal/web/opactions.go b/hub/internal/web/opactions.go index 3bd4419c..85e1167c 100644 --- a/hub/internal/web/opactions.go +++ b/hub/internal/web/opactions.go @@ -1,6 +1,7 @@ package web import ( + "fmt" "net/http" "strconv" "strings" @@ -19,6 +20,9 @@ const opActionsShown = 10 // operatorActor names who pressed, for the log and the row: the channel and the address. The hub has // one operator password and no user names, so this is the most it can say — never a credential. func operatorActor(r *http.Request) string { + if t := requestToken(r); t != nil { // 2026-10-29: a token press was recorded as „basic auth" — name the token + return fmt.Sprintf("access token %q from %s", t.Name, bindClientIP(r)) + } channel := "operator CLI (basic auth)" if _, err := r.Cookie(SessionCookieName); err == nil { channel = "operator browser session"